Skip to content

Commit

Permalink
Qute type-safe checks - take interface default methods into account
Browse files Browse the repository at this point in the history
- resolves quarkusio#16337
- also replace some deprecated APIs from tests
  • Loading branch information
mkouba committed Apr 8, 2021
1 parent df5d31a commit 7e3832b
Show file tree
Hide file tree
Showing 6 changed files with 150 additions and 57 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -1578,7 +1578,9 @@ private static AnnotationTarget findTemplateExtensionMethod(Info info, Type matc
* @return the property or null
*/
private static AnnotationTarget findProperty(String name, ClassInfo clazz, IndexView index) {
Set<DotName> interfaceNames = new HashSet<>();
while (clazz != null) {
interfaceNames.addAll(clazz.interfaceNames());
// Fields
for (FieldInfo field : clazz.fields()) {
if (!Modifier.isPublic(field.flags()) || ValueResolverGenerator.isSynthetic(field.flags())) {
Expand Down Expand Up @@ -1608,6 +1610,22 @@ private static AnnotationTarget findProperty(String name, ClassInfo clazz, Index
clazz = index.getClassByName(clazz.superName());
}
}
// Try the default methods
for (DotName interfaceName : interfaceNames) {
ClassInfo interfaceClassInfo = index.getClassByName(interfaceName);
if (interfaceClassInfo != null) {
for (MethodInfo method : interfaceClassInfo.methods()) {
// A default method is a public non-abstract instance method
if (Modifier.isPublic(method.flags()) && !Modifier.isStatic(method.flags())
&& !ValueResolverGenerator.isSynthetic(method.flags()) && !Modifier.isAbstract(method.flags())
&& (method.name().equals(name)
|| ValueResolverGenerator.getPropertyName(method.name()).equals(name))) {
return method;
}
}
}
}
// No matching method found
return null;
}

Expand All @@ -1624,61 +1642,14 @@ private static AnnotationTarget findProperty(String name, ClassInfo clazz, Index
*/
private static AnnotationTarget findMethod(VirtualMethodPart virtualMethod, ClassInfo clazz, Expression expression,
IndexView index, Function<String, String> templateIdToPathFun, Map<String, Match> results) {
Set<DotName> interfaceNames = new HashSet<>();
while (clazz != null) {
interfaceNames.addAll(clazz.interfaceNames());
for (MethodInfo method : clazz.methods()) {
if (Modifier.isPublic(method.flags()) && !Modifier.isStatic(method.flags())
&& !ValueResolverGenerator.isSynthetic(method.flags())
&& method.name().equals(virtualMethod.getName())) {
boolean isVarArgs = ValueResolverGenerator.isVarArgs(method);
List<Type> parameters = method.parameters();
int lastParamIdx = parameters.size() - 1;

if (isVarArgs) {
// For varargs methods match the minimal number of params
if (lastParamIdx > virtualMethod.getParameters().size()) {
continue;
}
} else {
if (virtualMethod.getParameters().size() != parameters.size()) {
// Number of params must be equal
continue;
}
}

// Check parameter types if available
boolean matches = true;
byte idx = 0;

for (Expression param : virtualMethod.getParameters()) {
Match result = results.get(param.toOriginalString());
if (result != null && !result.isEmpty()) {
// Type info available - validate parameter type
Type paramType;
if (isVarArgs && idx >= lastParamIdx) {
// Replace the type for varargs methods
paramType = parameters.get(lastParamIdx).asArrayType().component();
} else {
paramType = parameters.get(idx);
}
if (!Types.isAssignableFrom(paramType,
result.type, index)) {
matches = false;
break;
}
} else {
LOGGER.debugf(
"Type info not available - skip validation for parameter [%s] of method [%s] for expression [%s] in template [%s] on line %s",
method.parameterName(idx),
method.declaringClass().name() + "#" + method,
expression.toOriginalString(),
templateIdToPathFun.apply(expression.getOrigin().getTemplateId()),
expression.getOrigin().getLine());
}
idx++;
}
if (matches) {
return method;
}
&& methodMatches(method, virtualMethod, expression, index, templateIdToPathFun, results)) {
return method;
}
}
DotName superName = clazz.superName();
Expand All @@ -1688,9 +1659,81 @@ private static AnnotationTarget findMethod(VirtualMethodPart virtualMethod, Clas
clazz = index.getClassByName(clazz.superName());
}
}
// Try the default methods
for (DotName interfaceName : interfaceNames) {
ClassInfo interfaceClassInfo = index.getClassByName(interfaceName);
if (interfaceClassInfo != null) {
for (MethodInfo method : interfaceClassInfo.methods()) {
// A default method is a public non-abstract instance method
if (Modifier.isPublic(method.flags()) && !Modifier.isStatic(method.flags())
&& !ValueResolverGenerator.isSynthetic(method.flags()) && !Modifier.isAbstract(method.flags())
&& methodMatches(method, virtualMethod, expression, index, templateIdToPathFun, results)) {
return method;
}
}
}
}
// No matching method found
return null;
}

private static boolean methodMatches(MethodInfo method, VirtualMethodPart virtualMethod, Expression expression,
IndexView index, Function<String, String> templateIdToPathFun, Map<String, Match> results) {

if (!method.name().equals(virtualMethod.getName())) {
return false;
}

boolean isVarArgs = ValueResolverGenerator.isVarArgs(method);
List<Type> parameters = method.parameters();
int lastParamIdx = parameters.size() - 1;

if (isVarArgs) {
// For varargs methods match the minimal number of params
if (lastParamIdx > virtualMethod.getParameters().size()) {
return false;
}
} else {
if (virtualMethod.getParameters().size() != parameters.size()) {
// Number of params must be equal
return false;
}
}

// Check parameter types if available
boolean matches = true;
byte idx = 0;

for (Expression param : virtualMethod.getParameters()) {
Match result = results.get(param.toOriginalString());
if (result != null && !result.isEmpty()) {
// Type info available - validate parameter type
Type paramType;
if (isVarArgs && idx >= lastParamIdx) {
// Replace the type for varargs methods
paramType = parameters.get(lastParamIdx).asArrayType().component();
} else {
paramType = parameters.get(idx);
}
if (!Types.isAssignableFrom(paramType,
result.type, index)) {
matches = false;
break;
}
} else {
LOGGER.debugf(
"Type info not available - skip validation for parameter [%s] of method [%s] for expression [%s] in template [%s] on line %s",
method.parameterName(idx),
method.declaringClass().name() + "#" + method,
expression.toOriginalString(),
templateIdToPathFun.apply(expression.getOrigin().getTemplateId()),
expression.getOrigin().getLine());
}
idx++;
}
return matches;
}

private void processsTemplateData(IndexView index, AnnotationInstance templateData, AnnotationTarget annotationTarget,
Set<DotName> controlled, Map<DotName, AnnotationInstance> uncontrolled, ValueResolverGenerator.Builder builder) {
AnnotationValue targetValue = templateData.value("target");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;

import io.quarkus.qute.CheckedTemplate;
import io.quarkus.qute.TemplateInstance;
import io.quarkus.qute.api.CheckedTemplate;
import io.quarkus.test.QuarkusUnitTest;

public class OrEmptyTest {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
import io.quarkus.qute.Engine;
import io.quarkus.qute.Location;
import io.quarkus.qute.Template;
import io.quarkus.qute.api.ResourcePath;
import io.quarkus.test.QuarkusUnitTest;

public class InjectionTest {
Expand Down Expand Up @@ -50,10 +49,10 @@ public static class SimpleBean {
@Inject
Template foo;

@ResourcePath("foo.qute.html")
@Location("foo.qute.html")
Template foo2;

@ResourcePath("bars/bar")
@Location("bars/bar")
Template bar;

@Location("bars/bar")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;

import io.quarkus.qute.CheckedTemplate;
import io.quarkus.qute.TemplateException;
import io.quarkus.qute.TemplateInstance;
import io.quarkus.qute.api.CheckedTemplate;
import io.quarkus.test.QuarkusUnitTest;

public class CheckedTemplateConflictTest {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package io.quarkus.qute.deployment.typesafe;

import static org.junit.jupiter.api.Assertions.assertEquals;

import javax.inject.Inject;

import org.jboss.shrinkwrap.api.ShrinkWrap;
import org.jboss.shrinkwrap.api.asset.StringAsset;
import org.jboss.shrinkwrap.api.spec.JavaArchive;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;

import io.quarkus.qute.Template;
import io.quarkus.test.QuarkusUnitTest;

public class DefaultMethodValidationSuccessTest {

@RegisterExtension
static final QuarkusUnitTest config = new QuarkusUnitTest()
.setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class)
.addClasses(Movie.class, MovieExtensions.class)
.addAsResource(new StringAsset(
"{@io.quarkus.qute.deployment.typesafe.DefaultMethodValidationSuccessTest$Name name}Hello {name.fullName()}::{name.fullName}!"),
"templates/name.html"));

@Inject
Template name;

@Test
public void testResult() {
// Validation succeeded! Yay!
assertEquals("Hello Name Surname::Name Surname!", name.data("name", new Name()).render());
}

public static class Name implements Something {

public String name() {
return "Name";
}
}

interface Something {

String name();

default String fullName() {
return name() + " Surname";
}
}

}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package io.quarkus.qute.deployment.typesafe;

import io.quarkus.qute.CheckedTemplate;
import io.quarkus.qute.TemplateInstance;
import io.quarkus.qute.api.CheckedTemplate;

public class Monks {

Expand Down

0 comments on commit 7e3832b

Please sign in to comment.