Skip to content

Commit

Permalink
Qute: fix evaluation of section parameters
Browse files Browse the repository at this point in the history
- iterators of a HashMap#entrySet() are not guaranteed to be consistent;
even if no modifications are made to the map
- this issue was discovered by @mohitbadve with the
https://github.com/TestingResearchIllinois/NonDex project
- see also quarkusio#43820
  • Loading branch information
mkouba committed Oct 15, 2024
1 parent af494da commit 32ac5c1
Showing 1 changed file with 26 additions and 18 deletions.
Original file line number Diff line number Diff line change
@@ -1,16 +1,13 @@
package io.quarkus.qute;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
import java.util.concurrent.CompletionStage;
import java.util.concurrent.ExecutionException;
import java.util.function.Supplier;
import java.util.stream.Collectors;

public final class Futures {

Expand Down Expand Up @@ -44,56 +41,67 @@ static CompletionStage<Map<String, Object>> evaluateParams(Map<String, Expressio
}
} else {
// multiple params
List<Entry<String, CompletableFuture<Object>>> asyncResults = new ArrayList<>(parameters.size());
for (Entry<String, Expression> e : parameters.entrySet()) {
if (!e.getValue().isLiteral()) {
asyncResults.add(Map.entry(e.getKey(), resolutionContext.evaluate(e.getValue()).toCompletableFuture()));
}
}
if (asyncResults.isEmpty()) {
Map<String, CompletableFuture<Object>> asyncResults = collectAsyncResults(parameters, resolutionContext);
if (asyncResults == null) {
// only literals
return CompletedStage.of(parameters.entrySet().stream()
.collect(Collectors.toMap(Entry::getKey, e -> e.getValue().getLiteral())));
Map<String, Object> ret = new HashMap<>();
for (Entry<String, Expression> e : parameters.entrySet()) {
ret.put(e.getKey(), e.getValue().getLiteral());
}
return CompletedStage.of(ret);
} else if (asyncResults.size() == 1) {
// single non-literal param - avoid CompletableFuture.allOf() and add remaining literals
return asyncResults.get(0).getValue().thenApply(v -> {
return asyncResults.values().iterator().next().thenApply(v -> {
try {
return singleValueMap(parameters, asyncResults.get(0));
return singleValueMap(parameters, asyncResults.entrySet().iterator().next());
} catch (InterruptedException | ExecutionException e1) {
throw new CompletionException(e1);
}
});
} else {
// multiple non-literal params
CompletableFuture<Map<String, Object>> result = new CompletableFuture<>();
CompletableFuture.allOf(asyncResults.stream().map(Entry::getValue).toArray(CompletableFuture[]::new))
CompletableFuture.allOf(asyncResults.values().toArray(CompletableFuture[]::new))
.whenComplete((v, t1) -> {
if (t1 != null) {
result.completeExceptionally(t1);
} else {
// IMPL NOTE: Keep the map mutable - it can be modified in UserTagSectionHelper
Map<String, Object> values = new HashMap<>();
int j = 0;
try {
for (Entry<String, Expression> entry : parameters.entrySet()) {
if (entry.getValue().isLiteral()) {
values.put(entry.getKey(), entry.getValue().getLiteral());
} else {
values.put(entry.getKey(), asyncResults.get(j++).getValue().get());
values.put(entry.getKey(), asyncResults.get(entry.getKey()).get());
}
}
result.complete(values);
} catch (Throwable e) {
result.completeExceptionally(e);
}

}
});
return result;
}
}
}

private static Map<String, CompletableFuture<Object>> collectAsyncResults(Map<String, Expression> parameters,
ResolutionContext resolutionContext) {
Map<String, CompletableFuture<Object>> asyncResults = null;
for (Entry<String, Expression> e : parameters.entrySet()) {
if (e.getValue().isLiteral()) {
continue;
}
if (asyncResults == null) {
asyncResults = new HashMap<>();
}
asyncResults.put(e.getKey(), resolutionContext.evaluate(e.getValue()).toCompletableFuture());
}
return asyncResults;
}

private static Map<String, Object> singleValueMap(String key, Object value) {
// IMPL NOTE: Keep the map mutable - it can be modified in UserTagSectionHelper
Map<String, Object> map = new HashMap<>();
Expand Down

0 comments on commit 32ac5c1

Please sign in to comment.