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

Fix native JsMethods with varargs called from Java varargs methods #9957

Merged
merged 13 commits into from
Aug 9, 2024

Conversation

niloc132
Copy link
Contributor

Native jsinterop varargs calls of the form instance.method(argsArray) sometimes implemented using instance.method.apply(instance, argsArray), where instance represents some expression. JsUtils.createApplyInvocation assumes that ImplementJsVarargs has already created a local variable to ensure that no side effects can take place when cloning the "instance", but ImplementJsVarargs doesn't actually check if a side effect was able to happen.

This fix only adds an additional check, creating a local variable in the case where the instance expression has side effects, and clarifies that a native JsProperty might have sideeffects (as it could call a JS property accessor).

An additional fix is made to JsSafeCloner, to let it successfully clone a qualified reference (e.g. $wnd.console) rather than produce an invalid JS AST. This ended up not being strictly required for the fix, but appears to be more correct and will at least generate code rather than a NullPointerException in a later location.

Fixes #9932

@niloc132
Copy link
Contributor Author

@niloc132 niloc132 added this to the 2.12 milestone May 15, 2024
@niloc132
Copy link
Contributor Author

niloc132 commented Jun 3, 2024

This patch has a few issues with it - the native property ref issue is one, discussed above, but also that the fix is incomplete. While this does appear to address the long-standing issue (at least since 2.9, probably older), it seems that the recent JDT update changes how varargs are handled in some small way. Perhaps that issue could be handled separately, but they seem somewhat linked - when I understand what is happening there I'll see if it makes sense to land them together or apart.

@niloc132 niloc132 marked this pull request as draft June 3, 2024 00:26
@niloc132 niloc132 force-pushed the 9932-varargs-callstyle-test branch from 01948a6 to 54cf49b Compare June 19, 2024 18:23
@niloc132
Copy link
Contributor Author

I think the concerns above are addressed - the native property ref sideeffect has been backed out, and the JDT issue is resolved, though more tests need to be written to confirm. Here's the fully build succeeding with the latest JDT change: https://github.com/niloc132/gwt/actions/runs/9586615514

The fix revolves around the fact that there are subtypes of MethodBinding that didn't previously exist - using MethodBinding.original() we can get back the underlying method so that we see that the method is varargs (so may need an array creation synthesized). However, original() also discards generic parameterization, so types no longer match, so this must be done somewhat carefully, and more tests are needed to confirm that the behavior and output is correct. At the very least, tests should include:

  • varargs with Object... (as this will accept all objects), tested with null, empty, one argument, several arguments, array
  • repeat with a non-vararg param
  • varargs with type args, where the typearg is unrelated to the vararg param
  • varargs with type args, where the typearg is used to specify the vararg param (like Arrays.asList)

Unfortunately, GWT has effectively no tests for simple Java features, only indirect tests that often expose problems. I assume either that these tests exist but were never ported to open source, or that they were not needed since Google had enough code written using GWT that such problems were discovered on their own. Nevertheless, it makes bugs like this and switch/case hard to hunt down, since simple tests like "are varargs methods called correctly" or "does the correct case get used in a switch" don't seem to exist in their plainest form.

@niloc132 niloc132 marked this pull request as ready for review July 17, 2024 12:25
@niloc132 niloc132 requested a review from vegegoku July 17, 2024 12:25
@@ -462,8 +462,8 @@ public void endVisit(JMethodCall x, Context ctx) {
// Passed as an array to varargs method will result in an apply call, in which case hoist the
// qualifier to make sure it is only evaluated once.
JExpression instance = x.getInstance();
if (x.getTarget().needsDynamicDispatch() && !x.isStaticDispatchOnly()
&& instance != null && !(instance instanceof JVariableRef)) {
if (x.getTarget().needsDynamicDispatch() && !x.isStaticDispatchOnly() && instance != null
Copy link
Contributor

Choose a reason for hiding this comment

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

Since we dropped support for java 7 and now we only support java 8+ wont it make sense to start using newer syntax like Objects.nonNull(...) and Objects.isNull(...)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Specifically, we dropped support for running this test on Java 7 - the code may or may not still run, but the test won't pass. We are not yet compiling code to use Java 9+ APIs, but could use Java features like that.

That said, two thoughts:

  • isNull() is great to use as a lambda, but it any better than the != null? It appears to be the same number of chars (assuming you add spaces, and assuming you do a static import), but will add the cost of a method call (until it is inlined by the jit, etc). As I understand it, the purpose is mostly for passing as a method reference...?
  • If I'm not changing the logic (I only moved the null check up a line), I'd rather not change the code to another way of doing the same thing, all else equals.

@@ -143,13 +143,17 @@ public void endVisit(JsNameOf x, JsContext ctx) {
*/
@Override
public void endVisit(JsNameRef x, JsContext ctx) {
if (x.getQualifier() == null && x.getIdent() == "arguments") {
if (x.getQualifier() == null && "arguments".equals(x.getIdent())) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Same here Objects.isNull

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Objects.equals() probably? I think that would be better than what I have here now, as a replacement for ==. I've never seen this fail, but it certainly could, and then would break here, which is why I changed it.

@niloc132 niloc132 requested a review from vegegoku August 1, 2024 16:50
@niloc132 niloc132 merged commit 2d0d956 into gwtproject:main Aug 9, 2024
3 checks passed
@vasvir
Copy link

vasvir commented Aug 21, 2024

Sorry if this is irrelevant:

Is this patch close to the issue #9675 ? Or is it in a different part of the code?

@jnehlmeier
Copy link
Member

@vasvir This patch fixes JsMethod varargs methods, e.g. console.log(Object... o), which caused compiler errors. This patch does not change how GWT generates JS code for Java lambdas.

@niloc132
Copy link
Contributor Author

@vasvir I think the issue you linked has an accurate summary here - there isn't a nice way to do this outside of the ones you've presented.

Getting into the details of "what does the Function object say about how you can call it" seem fraught - either we need an extra wrapper per function (Roberto's solution of a new wrapper per func), a huge runtime cost (your option 2 or 3 solution) or we need to be satisfied with only 0-10 args, and never preserving the name of the arguments (your option 1). While 0-10 (or maybe 0-5? 0-50?) might suffice for your own work, it would break JS projects/tools that inspect the names params would still break (angular does this for example for its DI feature).

Another option not included in your list is to get rid of the runtime arg count check and instead have your 11 (or 51, etc) makeLambdaArgumentN functions in Runtime.java, and tweak GenerateJavaScriptAST.constructJsFunctionObject

private JsExpression constructJsFunctionObject(SourceInfo sourceInfo, JClassType type,
JsName ctorName, JsNameRef ctorReference, JsExpression ctorArguments) {
// Foo.prototype.functionMethodName
JMethod jsFunctionMethod = getJsFunctionMethod(type);
JsNameRef funcNameRef = JsUtils.createQualifiedNameRef(sourceInfo,
ctorName, prototype, polymorphicNames.get(jsFunctionMethod));
// makeLambdaFunction(Foo.prototype.functionMethodName, new Foo(...))
return constructInvocation(sourceInfo, RuntimeConstants.RUNTIME_MAKE_LAMBDA_FUNCTION,
funcNameRef, ctorReference, ctorArguments);
}

to instead dispatch based on arg count, so the work is done at compile time.

In theory this could be an optional feature this way, and the 99% of apps that have never wanted this don't need to be impacted by this case.

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.

Regression in jsinterop varargs causing internal compiler error
4 participants