-
Notifications
You must be signed in to change notification settings - Fork 1.9k
GROOVY-11737: Improve clarity of Groovy main method selection priority #2428
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
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -259,39 +259,29 @@ private Object runScriptOrMainOrTestOrRunnable(Class scriptClass, String[] args) | |||||||||||||||||||||
| // ignore instantiation errors, try to do main | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| try { | ||||||||||||||||||||||
| // let's find a String[] main method | ||||||||||||||||||||||
| Method stringArrayMain = scriptClass.getMethod(MAIN_METHOD_NAME, String[].class); | ||||||||||||||||||||||
| // if that main method exists, invoke it | ||||||||||||||||||||||
| if (Modifier.isStatic(stringArrayMain.getModifiers())) { | ||||||||||||||||||||||
| return InvokerHelper.invokeStaticMethod(scriptClass, MAIN_METHOD_NAME, new Object[]{args}); | ||||||||||||||||||||||
| } else { | ||||||||||||||||||||||
| Object script = InvokerHelper.invokeNoArgumentsConstructorOf(scriptClass); | ||||||||||||||||||||||
| return InvokerHelper.invokeMethod(script, MAIN_METHOD_NAME, args); | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| } catch (NoSuchMethodException ignore) { } | ||||||||||||||||||||||
| try { | ||||||||||||||||||||||
| // let's find an Object main method | ||||||||||||||||||||||
| Method stringArrayMain = scriptClass.getMethod(MAIN_METHOD_NAME, Object.class); | ||||||||||||||||||||||
| // if that main method exists, invoke it | ||||||||||||||||||||||
| if (Modifier.isStatic(stringArrayMain.getModifiers())) { | ||||||||||||||||||||||
| return InvokerHelper.invokeStaticMethod(scriptClass, MAIN_METHOD_NAME, new Object[]{args}); | ||||||||||||||||||||||
| } else { | ||||||||||||||||||||||
| Object script = InvokerHelper.invokeNoArgumentsConstructorOf(scriptClass); | ||||||||||||||||||||||
| return InvokerHelper.invokeMethod(script, MAIN_METHOD_NAME, new Object[]{args}); | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| } catch (NoSuchMethodException ignore) { } | ||||||||||||||||||||||
| try { | ||||||||||||||||||||||
| // let's find a no-arg main method | ||||||||||||||||||||||
| Method noArgMain = scriptClass.getMethod(MAIN_METHOD_NAME); | ||||||||||||||||||||||
| // if that main method exists, invoke it | ||||||||||||||||||||||
| if (Modifier.isStatic(noArgMain.getModifiers())) { | ||||||||||||||||||||||
| return InvokerHelper.invokeStaticNoArgumentsMethod(scriptClass, MAIN_METHOD_NAME); | ||||||||||||||||||||||
| } else { | ||||||||||||||||||||||
| Object script = InvokerHelper.invokeNoArgumentsConstructorOf(scriptClass); | ||||||||||||||||||||||
| return InvokerHelper.invokeMethod(script, MAIN_METHOD_NAME, EMPTY_ARGS); | ||||||||||||||||||||||
| // Select main method using JEP-512 priority: static before instance, args before no-args. | ||||||||||||||||||||||
| // Uses Method.invoke() directly to avoid Groovy multimethod dispatch selecting a different overload. | ||||||||||||||||||||||
| Method selected = findMainMethod(scriptClass); | ||||||||||||||||||||||
| if (selected != null) { | ||||||||||||||||||||||
| try { | ||||||||||||||||||||||
| selected.setAccessible(true); | ||||||||||||||||||||||
| if (Modifier.isStatic(selected.getModifiers())) { | ||||||||||||||||||||||
| return selected.getParameterCount() == 0 | ||||||||||||||||||||||
| ? selected.invoke(null) | ||||||||||||||||||||||
| : selected.invoke(null, (Object) args); | ||||||||||||||||||||||
| } else { | ||||||||||||||||||||||
| Object instance = InvokerHelper.invokeNoArgumentsConstructorOf(scriptClass); | ||||||||||||||||||||||
| return selected.getParameterCount() == 0 | ||||||||||||||||||||||
| ? selected.invoke(instance) | ||||||||||||||||||||||
| : selected.invoke(instance, (Object) args); | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| } catch (InvocationTargetException e) { | ||||||||||||||||||||||
| throw e.getCause() instanceof RuntimeException re ? re | ||||||||||||||||||||||
| : new InvokerInvocationException(e); | ||||||||||||||||||||||
|
Comment on lines
+279
to
+280
|
||||||||||||||||||||||
| throw e.getCause() instanceof RuntimeException re ? re | |
| : new InvokerInvocationException(e); | |
| Throwable cause = e.getCause(); | |
| if (cause instanceof RuntimeException re) { | |
| throw re; | |
| } | |
| if (cause instanceof Error err) { | |
| throw err; | |
| } | |
| throw new InvokerInvocationException(cause); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -509,11 +509,14 @@ private MethodNode findRun() { | |
| * We retain the 'main' method if a compatible one is found. | ||
| * A compatible one has no parameters or 1 (Object or String[]) parameter. | ||
| * The return type must be void or Object. | ||
| * When multiple valid main methods exist, a warning is issued for those | ||
| * that would not be reachable from the command-line runner. | ||
| * Priority follows JEP-512: static before instance, args before no-args. | ||
| */ | ||
| private MethodNode handleMainMethodIfPresent(final List<MethodNode> methods) { | ||
| boolean foundInstance = false; | ||
| boolean foundStatic = false; | ||
| MethodNode result = null; | ||
| List<MethodNode> validMains = new ArrayList<>(); | ||
| for (MethodNode node : methods) { | ||
| if ("main".equals(node.getName()) && !node.isPrivate()) { | ||
| int numParams = node.getParameters().length; | ||
|
Comment on lines
517
to
522
|
||
|
|
@@ -527,16 +530,33 @@ private MethodNode handleMainMethodIfPresent(final List<MethodNode> methods) { | |
| if (node.isStatic() ? foundStatic : foundInstance) { | ||
| throw new RuntimeException("Repetitive main method found."); | ||
| } | ||
| if (!foundStatic) { // static trumps instance | ||
| result = node; | ||
| } | ||
| validMains.add(node); | ||
|
|
||
| if (node.isStatic()) foundStatic = true; | ||
| else foundInstance = true; | ||
|
Comment on lines
530
to
536
|
||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| if (validMains.isEmpty()) return null; | ||
|
|
||
| // Select winner using JEP-512 priority: static before instance, args before no-args | ||
| validMains.sort((a, b) -> { | ||
| if (a.isStatic() != b.isStatic()) return a.isStatic() ? -1 : 1; | ||
| return Integer.compare(b.getParameters().length, a.getParameters().length); | ||
| }); | ||
|
Comment on lines
+544
to
+548
|
||
| MethodNode result = validMains.get(0); | ||
|
|
||
| // Warn about unreachable main methods | ||
| for (int i = 1; i < validMains.size(); i++) { | ||
| MethodNode unreachable = validMains.get(i); | ||
| getContext().addWarning("Method '" + unreachable.getText() | ||
| + "' is not reachable from the Groovy runner" | ||
| + " because a higher-priority main method '" | ||
| + result.getText() + "' exists", unreachable); | ||
|
Comment on lines
+554
to
+557
|
||
| } | ||
|
|
||
| return result; | ||
| } | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
findMainMethodusesClass#getMethod, which returns only public methods, sosetAccessible(true)should be unnecessary. Keeping it can introduce avoidable failures under restrictive security/module settings; consider removing it (or only setting accessible when usinggetDeclaredMethod).