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

Remove usages of deprecated (in Java 9 and above) Class#newInstance #5483

Merged
merged 3 commits into from
May 16, 2021

Conversation

basil
Copy link
Member

@basil basil commented May 12, 2021

Problem

The documentation for Class#newInstance includes the following warning:

Note that this method propagates any exception thrown by the nullary constructor, including a checked exception. Use of this method effectively bypasses the compile-time exception checking that would otherwise be performed by the compiler. The Constructor#newInstance method avoids this problem by wrapping any exception thrown by the constructor in a (checked) InvocationTargetException.

For this reason, the Class#newInstance method has been deprecated in Java 9 and above.

Solution

We should always prefer myClass.getConstructor().newInstance() to calling myClass.newInstance() directly. The latter sequence of calls is inferred to be able to throw the additional exception types InvocationTargetException and NoSuchMethodException. Both of these exception types are subclasses of ReflectiveOperationException. This PR updates our usages accordingly.

Bonus

In addition, I noticed this codebase was using a mixture of Error and AssertionError for "impossible" reflection errors. Error Prone recommends against this in favor of LinkageError for rethrowing ReflectiveOperationException exceptions as unchecked. While the reasoning isn't explicitly stated, presumably it is because LinkageError is more specific. I've updated our usage to match this recommendation.

Proposed changelog entries

N/A

Proposed upgrade guidelines

N/A

Maintainer checklist

Before the changes are marked as ready-for-merge:

  • There are at least 2 approvals for the pull request and no outstanding requests for change
  • Conversations in the pull request are over OR it is explicit that a reviewer does not block the change
  • Changelog entries in the PR title and/or Proposed changelog entries are correct
  • Proper changelog labels are set so that the changelog can be generated automatically
  • If the change needs additional upgrade steps from users, upgrade-guide-needed label is set and there is a Proposed upgrade guidelines section in the PR title. (example)
  • If it would make sense to backport the change to LTS, a Jira issue must exist, be a Bug or Improvement, and be labeled as lts-candidate to be considered (see query).

@basil basil added the skip-changelog Should not be shown in the changelog label May 12, 2021
Copy link
Member

@jglick jglick left a comment

Choose a reason for hiding this comment

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

Fine if it avoids a handful of deprecation warnings whenever we do switch the codebase to -source 11. The loss of checked exceptions from newInstance() calls was not actually a problem at all—if you are using reflection obviously you do not know or care what throws clause the constructor used.

core/src/main/java/hudson/cli/Connection.java Outdated Show resolved Hide resolved
Comment on lines +84 to +96
} catch (InvocationTargetException e) {
Throwable t = e.getCause();
if (t instanceof RuntimeException) {
throw (RuntimeException) t;
} else if (t instanceof IOException) {
throw new UncheckedIOException((IOException) t);
} else if (t instanceof Exception) {
throw new RuntimeException(t);
} else if (t instanceof Error) {
throw (Error) t;
} else {
throw new Error(e);
}
Copy link
Member

Choose a reason for hiding this comment

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

Why checked exceptions were a terrible idea: Exhibit A

Copy link
Member Author

Choose a reason for hiding this comment

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

Indeed. I was tempted to use Cy Young. That's probably what Kohsuke would have done back in the day. 😊 But I decided to keep it simple.

Copy link
Member

Choose a reason for hiding this comment

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

This is probably a case when introducing a new Checked Exception throwing method AND deprecating the old one is reasonable. I do not intend to start a holywar here though :)

Copy link
Member

Choose a reason for hiding this comment

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

In this case the appropriate code would really be more like

try {
    ClassLoader cl = Jenkins.get().getPluginManager().uberClassLoader;
    instance = (Lifecycle)cl.loadClass(p).getDeclaredConstructor().newInstance();
} catch (Throwable t) {
    LOGGER.log(Level.WARNING, "Could not load requested ${hudson.lifecycle}", t);
    instance = new ExitLifecycle();
}

core/src/main/java/hudson/util/DescriptorList.java Outdated Show resolved Hide resolved
@timja timja added the ready-for-merge The PR is ready to go, and it will be merged soon if there is no negative feedback label May 13, 2021
@timja
Copy link
Member

timja commented May 13, 2021

This PR is now ready for merge, after ~24 hours, we will merge it if there's no negative feedback.

Thanks!

@basil basil added the squash-merge-me Unclean or useless commit history, should be merged only with squash-merge label May 13, 2021
Copy link
Member

@oleg-nenashev oleg-nenashev left a comment

Choose a reason for hiding this comment

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

No objections though I would have implemented it differently

Comment on lines +84 to +96
} catch (InvocationTargetException e) {
Throwable t = e.getCause();
if (t instanceof RuntimeException) {
throw (RuntimeException) t;
} else if (t instanceof IOException) {
throw new UncheckedIOException((IOException) t);
} else if (t instanceof Exception) {
throw new RuntimeException(t);
} else if (t instanceof Error) {
throw (Error) t;
} else {
throw new Error(e);
}
Copy link
Member

Choose a reason for hiding this comment

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

This is probably a case when introducing a new Checked Exception throwing method AND deprecating the old one is reasonable. I do not intend to start a holywar here though :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
ready-for-merge The PR is ready to go, and it will be merged soon if there is no negative feedback skip-changelog Should not be shown in the changelog squash-merge-me Unclean or useless commit history, should be merged only with squash-merge
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants