From cb2305f907f69d045ce381c011f54a773751f54e Mon Sep 17 00:00:00 2001 From: Martin Kouba Date: Mon, 19 Apr 2021 10:29:55 +0200 Subject: [PATCH] Qute - fix validation of type-safe message expressions - NPE is currently thrown if a type-safe message expression references a parent expression via hint - resolves #16590 --- .../deployment/MessageBundleProcessor.java | 11 ++++++-- .../qute/deployment/QuteProcessor.java | 4 +++ .../TemplateExpressionMatchesBuildItem.java | 27 +++++++++++++++++++ ...undleTemplateExpressionValidationTest.java | 9 +++++-- 4 files changed, 47 insertions(+), 4 deletions(-) create mode 100644 extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/TemplateExpressionMatchesBuildItem.java diff --git a/extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/MessageBundleProcessor.java b/extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/MessageBundleProcessor.java index cd87e2706aa32..56b878887aa80 100644 --- a/extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/MessageBundleProcessor.java +++ b/extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/MessageBundleProcessor.java @@ -315,6 +315,7 @@ void validateMessageBundleMethodsInTemplates(TemplatesAnalysisBuildItem analysis List excludes, List messageBundles, List messageBundleMethods, + List expressionMatches, BuildProducer incorrectExpressions, BuildProducer implicitClasses) { @@ -356,7 +357,13 @@ public String apply(String id) { for (Entry> exprEntry : expressions.entrySet()) { - Map generatedIdsToMatches = new HashMap<>(); + Map generatedIdsToMatches = Collections.emptyMap(); + for (TemplateExpressionMatchesBuildItem templateExpressionMatchesBuildItem : expressionMatches) { + if (templateExpressionMatchesBuildItem.templateGeneratedId.equals(exprEntry.getKey().generatedId)) { + generatedIdsToMatches = templateExpressionMatchesBuildItem.getGeneratedIdsToMatches(); + break; + } + } for (Expression expression : exprEntry.getValue()) { // msg:hello_world(foo.name) @@ -407,7 +414,7 @@ public String apply(String id) { incorrectExpressions, expression, index, implicitClassToMembersUsed, templateIdToPathFun, generatedIdsToMatches); Match match = results.get(param.toOriginalString()); - if (match != null && !Types.isAssignableFrom(match.type(), + if (match != null && !match.isEmpty() && !Types.isAssignableFrom(match.type(), methodParams.get(idx), index)) { incorrectExpressions .produce(new IncorrectExpressionBuildItem(expression.toOriginalString(), diff --git a/extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/QuteProcessor.java b/extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/QuteProcessor.java index f08f9e8b26137..3affcbcdf4721 100644 --- a/extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/QuteProcessor.java +++ b/extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/QuteProcessor.java @@ -484,6 +484,7 @@ void validateExpressions(TemplatesAnalysisBuildItem templatesAnalysis, List excludes, BuildProducer incorrectExpressions, BuildProducer implicitClasses, + BuildProducer expressionMatches, BeanDiscoveryFinishedBuildItem beanDiscovery, List checkedTemplates, QuteConfig config) { @@ -559,6 +560,9 @@ public String apply(String id) { generatedIdsToMatches)); } } + + expressionMatches + .produce(new TemplateExpressionMatchesBuildItem(templateAnalysis.generatedId, generatedIdsToMatches)); } for (Entry> entry : implicitClassToMembersUsed.entrySet()) { diff --git a/extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/TemplateExpressionMatchesBuildItem.java b/extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/TemplateExpressionMatchesBuildItem.java new file mode 100644 index 0000000000000..81310e0e55f1d --- /dev/null +++ b/extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/TemplateExpressionMatchesBuildItem.java @@ -0,0 +1,27 @@ +package io.quarkus.qute.deployment; + +import java.util.Map; + +import io.quarkus.builder.item.MultiBuildItem; +import io.quarkus.qute.deployment.QuteProcessor.Match; + +final class TemplateExpressionMatchesBuildItem extends MultiBuildItem { + + final String templateGeneratedId; + + private final Map generatedIdsToMatches; + + public TemplateExpressionMatchesBuildItem(String templateGeneratedId, Map generatedIdsToMatches) { + this.templateGeneratedId = templateGeneratedId; + this.generatedIdsToMatches = generatedIdsToMatches; + } + + Match getMatch(Integer generatedId) { + return generatedIdsToMatches.get(generatedId); + } + + Map getGeneratedIdsToMatches() { + return generatedIdsToMatches; + } + +} diff --git a/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/i18n/MessageBundleTemplateExpressionValidationTest.java b/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/i18n/MessageBundleTemplateExpressionValidationTest.java index 6f1f3b9d60589..1ea5ff011fc58 100644 --- a/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/i18n/MessageBundleTemplateExpressionValidationTest.java +++ b/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/i18n/MessageBundleTemplateExpressionValidationTest.java @@ -20,7 +20,8 @@ public class MessageBundleTemplateExpressionValidationTest { static final QuarkusUnitTest config = new QuarkusUnitTest() .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class) .addClasses(MyBundle.class, Item.class) - .addAsResource(new StringAsset("{msg:hello('foo')} {msg:hello_and_bye} {msg:hello(1,2)}"), + .addAsResource(new StringAsset( + "{@java.util.List names} {msg:hello('foo')} {msg:hello_and_bye} {msg:hello(1,2)} {#each names}{msg:helloName(it)}{/each}"), "templates/hello.html")) .assertException(t -> { Throwable e = t; @@ -35,10 +36,11 @@ public class MessageBundleTemplateExpressionValidationTest { if (te == null) { fail("No template exception thrown: " + t); } - assertTrue(te.getMessage().contains("Found template problems (3)"), te.getMessage()); + assertTrue(te.getMessage().contains("Found template problems (4)"), te.getMessage()); assertTrue(te.getMessage().contains("msg:hello('foo')"), te.getMessage()); assertTrue(te.getMessage().contains("msg:hello_and_bye"), te.getMessage()); assertTrue(te.getMessage().contains("msg:hello(1,2)"), te.getMessage()); + assertTrue(te.getMessage().contains("msg:helloName(it)"), te.getMessage()); }); @Test @@ -52,6 +54,9 @@ public interface MyBundle { @Message("Hello {item.name}") String hello(Item item); + @Message("Hello {name}!") + String helloName(String name); + } }