Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow Thymeleaf to check for allowed method access of Java supertypes #474

Merged
merged 2 commits into from
Jun 6, 2024

Conversation

jkuipers
Copy link
Contributor

@jkuipers jkuipers commented Apr 5, 2024

What does this PR do?

To prevent Thymeleaf templates from accessing methods on built-in Java types that are not allowed, it ships with a list of allowed supertypes. Its org.thymeleaf.util.ExpressionUtils type has an isMemberAllowed method that calls getDeclaredMethods() on those types, so that needs to work for all these types in a native image. This PR ensures that.

Recent versions of Thymeleaf have expanded the list with two additional types, which is why the M2 version has 7 new entries while the RC1 / latest has 9. For details, check out
the source code.

Code sections where the PR accesses files, network, docker or some external service

None.

Checklist before merging

@jkuipers jkuipers requested a review from a team as a code owner April 5, 2024 18:42
@jkuipers jkuipers requested a review from melix April 5, 2024 18:42
@fniephaus
Copy link
Member

Thanks a lot for contributing, @jkuipers!
Would be great if you could make sure the relevant code is exercised in the corresponding tests, so that this cannot accidentally break: https://github.com/oracle/graalvm-reachability-metadata/tree/master/tests/src/org.thymeleaf/thymeleaf

@jkuipers
Copy link
Contributor Author

jkuipers commented Apr 6, 2024

Happy to add some tests. Note that this code is only exercised by the framework when you use Spring or OGNL expressions, but the required changes have to go in the core project. @fniephaus is it sufficient to simply add some tests that invoke the org.thymeleaf.util.ExpressionUtils#isMemberAllowed method with objects of the relevant types? If not, the tests for the core framework would require additional dependencies to bring in OGNL so that I can call the OGNLVariableExpressionEvaluator, which to me seems like overkill and unnecessary complexity.

@fniephaus
Copy link
Member

No need to pull in addition dependencies if you can invoke the code somehow directly, that is without accessing any private APIs reflectively or so.

@jkuipers jkuipers force-pushed the thymeleaf-java-supertypes branch from add5166 to 318fc39 Compare April 6, 2024 21:42
@jkuipers
Copy link
Contributor Author

jkuipers commented Apr 6, 2024

I added tests. Note that I had to update the existing date-based test, because it failed on my local machine (which uses a Dutch locale) without an explicit format.

@jkuipers jkuipers force-pushed the thymeleaf-java-supertypes branch 2 times, most recently from e901560 to 960bf9a Compare April 15, 2024 07:38
@jkuipers
Copy link
Contributor Author

Hey, is anything still missing that's causing this to stay open? I was hoping that this could get merged in its current state.

@jkuipers jkuipers force-pushed the thymeleaf-java-supertypes branch from 960bf9a to cf575c2 Compare April 19, 2024 11:43
Copy link
Member

@melix melix left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I order to approve or not, I need to understand a bit better why this is needed. This adds reflection metadata for all public methods of types from the JDK (guarded by the ExpressionUtils access), which can be quite expensive. Is this really needed or can we narrow more the methods which would be accessed reflectively?

@jkuipers
Copy link
Contributor Author

jkuipers commented Apr 24, 2024

I order to approve or not, I need to understand a bit better why this is needed. This adds reflection metadata for all public methods of types from the JDK (guarded by the ExpressionUtils access), which can be quite expensive. Is this really needed or can we narrow more the methods which would be accessed reflectively?

Hey, thanks for getting back to me. The Thymeleaf ExpressionUtils class is used when a template uses an expression (via Spring expression language or OGNL) to check whether that's an allowed call. It contains a hardcoded list of Java types for which it allows calls by reflectively iterating over all their declared methods and comparing the resulting names to the memberName that's being passed to the method.
Without the declared methods being available in the GraalVM metadata, the check will simply always fail when those methods are called from a template and won't allow the method to be accessed. I ran into this while iterating over the entries of a map and rendering their keys and values. Something like this then fails:

 <tr th:each="entry: ${queuesByName}">
  <td th:text="${entry.key}">queue name</td>

Of course I cannot predict what declared methods of those types would be used in practice by all possible Thymeleaf users, so my PR is based on wanting the ExpressionUtils to simply behave the same as when running on a JVM.
Note that I am myself not associated with the Thymeleaf project: I'm just a user that expected the Spring/Thymeleaf combination to work with GraalVM, as the Spring team has already provided some basic reachability data for both Thymeleaf itself as well as the Spring support for it here. However, that's not sufficient when you use basic functionality like what I showed here. When I ran into this I analyzed why this failed and what Thymeleaf needs to make this work in a native image and then created this PR by hand (i.e. not using any agents or other tools) based on the contents of the ExpressionUtils class. I don't see how you could limit this further based on the code that's in the ExpressionUtils class.
My understanding is that GraalVM won't include the ExpressionUtils when it's not actually used by the code being compiled, so this seemed like a good guarding clause.

@jkuipers
Copy link
Contributor Author

jkuipers commented May 7, 2024

@melix Hey, did you manage to have a look at my explanation and the Thymeleaf code that this PR allows to work in a native image? Without this, most real-world applications using Thymeleaf will not work correctly, which is extra confusing since this repo does already contain reachability metadata for it (I had assumed that this would've worked out the box when I started to use this).

In general I think that for a templating solution that relies on reflection it seems reasonable that some additional metadata is included, since you cannot predict what access templates will require, but in this particular case Thymeleaf even actively denies calls to common Java operations (esp. calls to collections, which are used all over the place in templates) without this metadata: it took me some time to figure this out and implement a fix in my own applications, it seems better to have this work out of the box and perhaps file an issue with the Thymeleaf team to optimize this for GraalVM in future releases so that the overhead can be reduced then.

@melix
Copy link
Member

melix commented May 15, 2024

@jkuipers sorry for the late answer, I'm back from a break. The explanation makes sense to me.

@jkuipers jkuipers force-pushed the thymeleaf-java-supertypes branch from cf575c2 to fd0b20f Compare May 16, 2024 12:11
To prevent Thymeleaf templates from accessing methods on built-in Java types
that are not allowed, it ships with a list of allowed supertypes.
Its org.thymeleaf.util.ExpressionUtils type has an isMemberAllowed method
that calls getDeclaredMethods() on those types, so that needs to work for
all these types in a native image. Without this addition neither Spring
support (using SpEL expressions) nor OGNL-based expressions work correctly.

Recent versions of Thymeleaf have expanded the list with two additional
types, which is why the M2 version has 7 new entries while the RC1 / latest
has 9. Obviously the tests don't cover these.
For details, check out
https://github.com/thymeleaf/thymeleaf/blob/3.1-master/lib/thymeleaf/src/main/java/org/thymeleaf/util/ExpressionUtils.java
@jkuipers jkuipers force-pushed the thymeleaf-java-supertypes branch from fd0b20f to 1b7af01 Compare May 16, 2024 12:13
@jkuipers
Copy link
Contributor Author

jkuipers commented Jun 6, 2024

@jkuipers sorry for the late answer, I'm back from a break. The explanation makes sense to me.

I'm a bit confused: I was assuming that that means that this can be merged, but the issue stays open and has been for two months now. Is anything else expected from my side?

@melix melix self-requested a review June 6, 2024 08:56
@melix melix merged commit 8bd6b11 into oracle:master Jun 6, 2024
15 checks passed
@melix
Copy link
Member

melix commented Jun 6, 2024

Thanks for your patience, I just merged it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants