Merge pull request #7277 from mkouba/issue-7144

Qute - ExtensionMethodGenerator now handles ClassCastException correctly
This commit is contained in:
Guillaume Smet
2020-02-20 23:42:03 +01:00
committed by GitHub
6 changed files with 75 additions and 18 deletions

View File

@@ -69,7 +69,7 @@ class EvaluatorImpl implements Evaluator {
return resolve(new EvalContextImpl(tryParent, ref, parts.next(), resolutionContext), resolvers.iterator())
.thenCompose(r -> {
if (parts.hasNext()) {
return resolveReference(false, r, parts, resolutionContext);
return resolveReference(tryParent, r, parts, resolutionContext);
} else {
return CompletableFuture.completedFuture(r);
}
@@ -82,7 +82,7 @@ class EvaluatorImpl implements Evaluator {
if (evalContext.tryParent && parent != null) {
// Continue with parent context
return resolve(
new EvalContextImpl(false, parent.getData(), evalContext.name, parent),
new EvalContextImpl(true, parent.getData(), evalContext.name, parent),
this.resolvers.iterator());
}
LOGGER.tracef("Unable to resolve %s", evalContext);

View File

@@ -47,7 +47,9 @@ public class LoopSectionHelper implements SectionHelper {
} else if (it instanceof Integer) {
iterator = IntStream.rangeClosed(1, (Integer) it).iterator();
} else {
throw new IllegalStateException("Cannot iterate over: " + it);
throw new IllegalStateException(
String.format("Cannot iterate over [%s] resolved for [%s] in template %s on line %s", it,
iterable.toOriginalString(), iterable.origin.getTemplateId(), iterable.origin.getLine()));
}
int idx = 0;
while (iterator.hasNext()) {

View File

@@ -68,8 +68,8 @@ public class LoopSectionTest {
@Test
public void testNestedLoops() {
List<String> data = new ArrayList<>();
data.add("alpha");
List<String> list = new ArrayList<>();
list.add("alpha");
Engine engine = Engine.builder()
.addSectionHelper(new LoopSectionHelper.Factory())
@@ -92,14 +92,14 @@ public class LoopSectionTest {
})
.build();
String template = "{#for name in this}"
String template = "{#for name in list}"
+ "{count}.{name}: {#for char in name.chars}"
+ "{name} - char at {index} = {char}{#if hasNext},{/}"
+ "{name} {global} char at {index} = {char}{#if hasNext},{/}"
+ "{/}{/}";
assertEquals(
"1.alpha: alpha - char at 0 = a,alpha - char at 1 = l,alpha - char at 2 = p,alpha - char at 3 = h,alpha - char at 4 = a",
engine.parse(template).render(data));
engine.parse(template).data("global", "-").data("list", list).render());
}
@Test

View File

@@ -8,12 +8,14 @@ import static org.objectweb.asm.Opcodes.ACC_PUBLIC;
import io.quarkus.gizmo.AssignableResultHandle;
import io.quarkus.gizmo.BranchResult;
import io.quarkus.gizmo.BytecodeCreator;
import io.quarkus.gizmo.CatchBlockCreator;
import io.quarkus.gizmo.ClassCreator;
import io.quarkus.gizmo.ClassOutput;
import io.quarkus.gizmo.FunctionCreator;
import io.quarkus.gizmo.MethodCreator;
import io.quarkus.gizmo.MethodDescriptor;
import io.quarkus.gizmo.ResultHandle;
import io.quarkus.gizmo.TryBlock;
import io.quarkus.qute.EvalContext;
import io.quarkus.qute.TemplateExtension;
import io.quarkus.qute.ValueResolver;
@@ -196,12 +198,20 @@ public class ExtensionMethodGenerator {
args[i + shift] = success.invokeVirtualMethod(Descriptors.COMPLETABLE_FUTURE_GET, paramResult);
}
ResultHandle invokeRet = success
// try
TryBlock tryCatch = success.tryBlock();
// catch (Throwable e)
CatchBlockCreator exception = tryCatch.addCatch(Throwable.class);
// CompletableFuture.completeExceptionally(Throwable)
exception.invokeVirtualMethod(Descriptors.COMPLETABLE_FUTURE_COMPLETE_EXCEPTIONALLY, whenRet,
exception.getCaughtException());
ResultHandle invokeRet = tryCatch
.invokeStaticMethod(MethodDescriptor.ofMethod(declaringClass.name().toString(), method.name(),
method.returnType().name().toString(),
method.parameters().stream().map(p -> p.name().toString()).collect(Collectors.toList()).toArray()),
args);
success.invokeVirtualMethod(Descriptors.COMPLETABLE_FUTURE_COMPLETE, whenRet, invokeRet);
tryCatch.invokeVirtualMethod(Descriptors.COMPLETABLE_FUTURE_COMPLETE, whenRet, invokeRet);
BytecodeCreator failure = throwableIsNull.falseBranch();
failure.invokeVirtualMethod(Descriptors.COMPLETABLE_FUTURE_COMPLETE_EXCEPTIONALLY, whenRet,

View File

@@ -1,6 +1,7 @@
package io.quarkus.qute.generator;
import io.quarkus.qute.TemplateData;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
@@ -50,4 +51,8 @@ public class MyService {
return CompletableFuture.completedFuture(param);
}
public static List<String> getDummy(MyService service, int limit, String dummy) {
return Collections.emptyList();
}
}

View File

@@ -4,6 +4,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.fail;
import io.quarkus.qute.Engine;
import io.quarkus.qute.EngineBuilder;
import io.quarkus.qute.EvalContext;
import io.quarkus.qute.Expression;
import io.quarkus.qute.ImmutableList;
@@ -11,19 +12,29 @@ import io.quarkus.qute.ValueResolver;
import java.io.IOException;
import java.io.InputStream;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import org.jboss.jandex.ClassInfo;
import org.jboss.jandex.DotName;
import org.jboss.jandex.Index;
import org.jboss.jandex.Indexer;
import org.jboss.jandex.MethodInfo;
import org.jboss.jandex.PrimitiveType;
import org.jboss.jandex.Type;
import org.jboss.jandex.Type.Kind;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
public class SimpleGeneratorTest {
static Set<String> generatedTypes = new HashSet<>();
@BeforeAll
public static void init() throws IOException {
TestClassOutput classOutput = new TestClassOutput();
@@ -31,11 +42,20 @@ public class SimpleGeneratorTest {
CompletionStage.class,
List.class);
ValueResolverGenerator generator = new ValueResolverGenerator(index, classOutput, Collections.emptyMap());
generator.generate(index.getClassByName(DotName.createSimple(MyService.class.getName())));
ClassInfo myServiceClazz = index.getClassByName(DotName.createSimple(MyService.class.getName()));
generator.generate(myServiceClazz);
generator.generate(index.getClassByName(DotName.createSimple(PublicMyService.class.getName())));
generator.generate(index.getClassByName(DotName.createSimple(MyItem.class.getName())));
generator.generate(index.getClassByName(DotName.createSimple(String.class.getName())));
generator.generate(index.getClassByName(DotName.createSimple(List.class.getName())));
generatedTypes.addAll(generator.getGeneratedTypes());
ExtensionMethodGenerator extensionMethodGenerator = new ExtensionMethodGenerator(classOutput);
MethodInfo extensionMethod = index.getClassByName(DotName.createSimple(MyService.class.getName())).method(
"getDummy", Type.create(myServiceClazz.name(), Kind.CLASS), PrimitiveType.INT,
Type.create(DotName.createSimple(String.class.getName()), Kind.CLASS));
extensionMethodGenerator.generate(extensionMethod, null);
generatedTypes.addAll(extensionMethodGenerator.getGeneratedTypes());
}
@Test
@@ -71,18 +91,38 @@ public class SimpleGeneratorTest {
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException expected) {
}
Engine engine = Engine.builder().addDefaults()
.addValueResolver(newResolver("io.quarkus.qute.generator.MyService_ValueResolver"))
.addValueResolver(newResolver("io.quarkus.qute.generator.PublicMyService_ValueResolver"))
.addValueResolver(newResolver("io.quarkus.qute.generator.MyItem_ValueResolver"))
.addValueResolver(newResolver("io.quarkus.qute.String_ValueResolver"))
.addValueResolver(newResolver("io.quarkus.qute.List_ValueResolver"))
.build();
EngineBuilder builder = Engine.builder().addDefaults();
for (String generatedType : generatedTypes) {
builder.addValueResolver(newResolver(generatedType));
}
Engine engine = builder.build();
assertEquals(" FOO ", engine.parse("{#if isActive} {name.toUpperCase} {/if}").render(new MyService()));
assertEquals("OK", engine.parse("{#if this.getList(5).size == 5}OK{/if}").render(new MyService()));
assertEquals("Martin NOT_FOUND OK NOT_FOUND",
engine.parse("{name} {surname} {isStatic ?: 'OK'} {base}").render(new PublicMyService()));
assertEquals("foo NOT_FOUND", engine.parse("{id} {bar}").render(new MyItem()));
try {
engine.parse("{this.getList(5,5)}").render(new MyService());
fail();
} catch (IllegalStateException e) {
assertClassCastException(e);
}
try {
engine.parse("{service.getDummy(5,resultNotFound)}").data("service", new MyService()).render();
fail();
} catch (IllegalStateException e) {
assertClassCastException(e);
}
}
private void assertClassCastException(Exception e) {
if (e.getCause() instanceof ExecutionException) {
ExecutionException ex = (ExecutionException) e.getCause();
if (ex.getCause() instanceof ClassCastException) {
return;
}
}
fail("Unexpected exception thrown: ", e);
}
private ValueResolver newResolver(String className)