diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 000000000000..2e71b1533092 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,7 @@ +root = true + +[*.{jelly, js, less, css, hbs}] +indent_style = space +indent_size = 2 +trim_trailing_whitespace = true +insert_final_newline = true diff --git a/.github/workflows/changelog.yml b/.github/workflows/changelog.yml index 1b60844219be..4991a9fd5cab 100644 --- a/.github/workflows/changelog.yml +++ b/.github/workflows/changelog.yml @@ -28,7 +28,7 @@ jobs: env: GITHUB_AUTH: github-actions:${{ secrets.GITHUB_TOKEN }} - name: Upload Changelog YAML - uses: actions/upload-artifact@v2.2.4 + uses: actions/upload-artifact@v2.3.0 with: name: changelog.yaml path: changelog.yaml diff --git a/bom/pom.xml b/bom/pom.xml index 059056f4a372..2584d153c91c 100644 --- a/bom/pom.xml +++ b/bom/pom.xml @@ -39,6 +39,7 @@ THE SOFTWARE. + 9.2 1.7.32 1593.v0e838714faae 2.4.12 @@ -95,8 +96,10 @@ THE SOFTWARE. com.google.inject - guice + guice-bom 5.0.1 + pom + import @@ -188,10 +191,35 @@ THE SOFTWARE. jbcrypt 1.0.0 + + org.ow2.asm + asm + ${asm.version} + + + org.ow2.asm + asm-analysis + ${asm.version} + + + org.ow2.asm + asm-commons + ${asm.version} + + + org.ow2.asm + asm-tree + ${asm.version} + + + org.ow2.asm + asm-util + ${asm.version} + com.github.jnr jnr-posix - 3.1.13 + 3.1.14 org.kohsuke diff --git a/cli/pom.xml b/cli/pom.xml index 3838594a564f..0a296a810e00 100644 --- a/cli/pom.xml +++ b/cli/pom.xml @@ -14,6 +14,10 @@ Command line interface for Jenkins https://github.com/jenkinsci/jenkins + + 2.8.0 + + @@ -68,7 +72,13 @@ org.apache.sshd sshd-core - 2.7.0 + ${mina-sshd.version} + true + + + org.apache.sshd + sshd-common + ${mina-sshd.version} true diff --git a/cli/src/main/java/hudson/cli/FullDuplexHttpStream.java b/cli/src/main/java/hudson/cli/FullDuplexHttpStream.java index 9096794e8f98..fec9d7d3b9d3 100644 --- a/cli/src/main/java/hudson/cli/FullDuplexHttpStream.java +++ b/cli/src/main/java/hudson/cli/FullDuplexHttpStream.java @@ -34,7 +34,6 @@ public InputStream getInputStream() { * A way to upload data to the server. * You will need to write to this and {@link OutputStream#flush} it to finish establishing a connection. */ - @SuppressFBWarnings("EI_EXPOSE_REP") public OutputStream getOutputStream() { return output; } diff --git a/cli/src/main/java/hudson/cli/SSHCLI.java b/cli/src/main/java/hudson/cli/SSHCLI.java index b5091e2e5754..d9fa4911df84 100644 --- a/cli/src/main/java/hudson/cli/SSHCLI.java +++ b/cli/src/main/java/hudson/cli/SSHCLI.java @@ -49,8 +49,8 @@ import org.apache.sshd.client.keyverifier.ServerKeyVerifier; import org.apache.sshd.client.session.ClientSession; import org.apache.sshd.common.future.WaitableFuture; -import org.apache.sshd.common.util.io.NoCloseInputStream; -import org.apache.sshd.common.util.io.NoCloseOutputStream; +import org.apache.sshd.common.util.io.input.NoCloseInputStream; +import org.apache.sshd.common.util.io.output.NoCloseOutputStream; import org.apache.sshd.common.util.security.SecurityUtils; /** diff --git a/core/pom.xml b/core/pom.xml index 4384bb506787..b85438f1c60b 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -42,8 +42,6 @@ THE SOFTWARE. true 2.2 2.8.3 - - High @@ -106,6 +104,26 @@ THE SOFTWARE. com.github.jnr jnr-posix + + org.ow2.asm + asm + + + org.ow2.asm + asm-analysis + + + org.ow2.asm + asm-commons + + + org.ow2.asm + asm-tree + + + org.ow2.asm + asm-util + org.kohsuke.stapler stapler diff --git a/core/src/main/java/hudson/ClassicPluginStrategy.java b/core/src/main/java/hudson/ClassicPluginStrategy.java index a7f3259bb525..523474a89d88 100644 --- a/core/src/main/java/hudson/ClassicPluginStrategy.java +++ b/core/src/main/java/hudson/ClassicPluginStrategy.java @@ -285,19 +285,18 @@ protected ClassLoader createClassLoader(List paths, ClassLoader parent) th * Creates the classloader that can load all the specified jar files and delegate to the given parent. */ protected ClassLoader createClassLoader(List paths, ClassLoader parent, Attributes atts) throws IOException { - if (atts != null) { - String usePluginFirstClassLoader = atts.getValue( "PluginFirstClassLoader" ); - if (Boolean.parseBoolean( usePluginFirstClassLoader )) { - PluginFirstClassLoader classLoader = new PluginFirstClassLoader(); - classLoader.setParentFirst( false ); - classLoader.setParent( parent ); - classLoader.addPathFiles( paths ); - return classLoader; - } - } + boolean usePluginFirstClassLoader = + atts != null && Boolean.parseBoolean(atts.getValue("PluginFirstClassLoader")); if (useAntClassLoader) { - AntClassLoader classLoader = new AntClassLoader(parent, true); + AntClassLoader classLoader; + if (usePluginFirstClassLoader) { + classLoader = new PluginFirstClassLoader(); + classLoader.setParentFirst(false); + classLoader.setParent(parent); + } else { + classLoader = new AntClassLoader(parent, true); + } classLoader.addPathFiles(paths); return classLoader; } else { @@ -305,7 +304,13 @@ protected ClassLoader createClassLoader(List paths, ClassLoader parent, At for (File path : paths) { urls.add(path.toURI().toURL()); } - return new URLClassLoader2(urls.toArray(new URL[0]), parent); + URLClassLoader2 classLoader; + if (usePluginFirstClassLoader) { + classLoader = new PluginFirstClassLoader2(urls.toArray(new URL[0]), parent); + } else { + classLoader = new URLClassLoader2(urls.toArray(new URL[0]), parent); + } + return classLoader; } } @@ -475,7 +480,7 @@ private static void parseClassPath(Manifest manifest, File archive, List p * Explodes the plugin into a directory, if necessary. */ private static void explode(File archive, File destDir) throws IOException { - destDir.mkdirs(); + Files.createDirectories(Util.fileToPath(destDir)); // timestamp check File explodeTime = new File(destDir,".timestamp2"); @@ -544,7 +549,7 @@ protected void zipDir(Resource dir, ZipOutputStream zOut, String vPath, }; z.setProject(prj); z.setTaskType("zip"); - classesJar.getParentFile().mkdirs(); + Files.createDirectories(Util.fileToPath(classesJar.getParentFile())); z.setDestFile(classesJar); z.add(mapper); z.execute(); diff --git a/core/src/main/java/hudson/ExtensionFinder.java b/core/src/main/java/hudson/ExtensionFinder.java index 0af3b4f17d9f..10ca19bef501 100644 --- a/core/src/main/java/hudson/ExtensionFinder.java +++ b/core/src/main/java/hudson/ExtensionFinder.java @@ -502,7 +502,7 @@ private void resolve(Class c, Set> encountered) { } } LOGGER.log(Level.FINER, "{0} looks OK", c); - } catch (Exception x) { + } catch (RuntimeException x) { throw new LinkageError("Failed to resolve "+c, x); } } diff --git a/core/src/main/java/hudson/ExtensionList.java b/core/src/main/java/hudson/ExtensionList.java index e5dd86b2c144..c453a46b3d30 100644 --- a/core/src/main/java/hudson/ExtensionList.java +++ b/core/src/main/java/hudson/ExtensionList.java @@ -357,7 +357,7 @@ private void fireOnChangeListeners() { for (ExtensionListListener listener : listeners) { try { listener.onChange(); - } catch (Exception e) { + } catch (Throwable e) { LOGGER.log(Level.SEVERE, "Error firing ExtensionListListener.onChange().", e); } } diff --git a/core/src/main/java/hudson/FilePath.java b/core/src/main/java/hudson/FilePath.java index fc6d6f39cbc7..5b79710b25f3 100644 --- a/core/src/main/java/hudson/FilePath.java +++ b/core/src/main/java/hudson/FilePath.java @@ -95,6 +95,7 @@ import java.nio.file.Paths; import java.nio.file.StandardCopyOption; import java.nio.file.attribute.FileAttribute; +import java.nio.file.attribute.FileTime; import java.nio.file.attribute.PosixFilePermission; import java.nio.file.attribute.PosixFilePermissions; import java.util.ArrayList; @@ -683,7 +684,7 @@ private static void unzip(File dir, InputStream in) throws IOException { unzip(dir,tmpFile); } finally { - tmpFile.delete(); + Files.delete(Util.fileToPath(tmpFile)); } } @@ -718,7 +719,7 @@ private static void unzip(File dir, File zipFile) throws IOException { } catch (InterruptedException ex) { LOGGER.log(Level.WARNING, "unable to set permissions", ex); } - f.setLastModified(e.getTime()); + Files.setLastModifiedTime(Util.fileToPath(f), e.getLastModifiedTime()); } } } finally { @@ -1035,7 +1036,7 @@ private boolean installIfNecessaryFrom(@NonNull URL archive, @NonNull TaskListen } // this reads from arbitrary URL - private final class Unpack extends MasterToSlaveFileCallable { + private static final class Unpack extends MasterToSlaveFileCallable { private final URL archive; Unpack(URL archive) { this.archive = archive; @@ -2426,7 +2427,7 @@ public Void invoke(File f, VirtualChannel channel) throws IOException { if(!deleting(reading(child)).renameTo(writing(creating(target)))) throw new IOException("Failed to rename "+child+" to "+target); } - deleting(tmp).delete(); + Files.deleteIfExists(Util.fileToPath(deleting(tmp))); return null; } } @@ -2687,6 +2688,7 @@ public Integer invoke(File base, VirtualChannel channel) throws IOException { scanner.scan(base, reading(new FileVisitor() { private boolean exceptionEncountered; private boolean logMessageShown; + @SuppressFBWarnings(value = "PATH_TRAVERSAL_IN", justification = "TODO needs triage") @Override public void visit(File f, String relativePath) throws IOException { if (f.isFile()) { @@ -2720,6 +2722,7 @@ private boolean tryCopyWithAttributes(File f, Path targetPath) { public boolean understandsSymlink() { return true; } + @SuppressFBWarnings(value = "PATH_TRAVERSAL_IN", justification = "TODO needs triage") @Override public void visitSymlink(File link, String target, String relativePath) throws IOException { try { @@ -2852,7 +2855,7 @@ private static void readFromTar(String name, File baseDir, InputStream in) throw } else { IOUtils.copy(t, writing(f)); - f.setLastModified(te.getModTime().getTime()); + Files.setLastModifiedTime(Util.fileToPath(f), FileTime.from(te.getModTime().toInstant())); int mode = te.getMode() & 0777; if (mode != 0 && !Functions.isWindows()) // be defensive _chmod(f, mode); @@ -2889,11 +2892,7 @@ public Boolean call() throws IOException { } /** - * Validates the ant file mask (like "foo/bar/*.txt, zot/*.jar") - * against this directory, and try to point out the problem. - * - *

- * This is useful in conjunction with {@link FormValidation}. + * Same as {@link #validateAntFileMask(String, int)} with (practically) unbounded number of operations. * * @return * null if no error was found. Otherwise returns a human readable error message. @@ -2907,28 +2906,45 @@ public String validateAntFileMask(final String fileMasks) throws IOException, In } /** - * Same as {@link #validateAntFileMask(String, int, boolean)} with caseSensitive set to true + * Same as {@link #validateAntFileMask(String, int, boolean)} with caseSensitive set to true. */ public String validateAntFileMask(final String fileMasks, final int bound) throws IOException, InterruptedException { return validateAntFileMask(fileMasks, bound, true); } + /** + * Same as {@link #validateAntFileMask(String, int, boolean)} with the default number of operations. + * @see #VALIDATE_ANT_FILE_MASK_BOUND + * @since TODO + */ + public String validateAntFileMask(final String fileMasks, final boolean caseSensitive) throws IOException, InterruptedException { + return validateAntFileMask(fileMasks, VALIDATE_ANT_FILE_MASK_BOUND, caseSensitive); + } + /** * Default bound for {@link #validateAntFileMask(String, int, boolean)}. * @since 1.592 */ - @SuppressFBWarnings("MS_SHOULD_BE_FINAL") + @SuppressFBWarnings(value = "MS_SHOULD_BE_FINAL", justification = "for script console") public static int VALIDATE_ANT_FILE_MASK_BOUND = SystemProperties.getInteger(FilePath.class.getName() + ".VALIDATE_ANT_FILE_MASK_BOUND", 10000); /** - * Like {@link #validateAntFileMask(String)} but performing only a bounded number of operations. + * Validates the ant file mask (like "foo/bar/*.txt, zot/*.jar") against this directory, and try to point out the problem. + * This performs only a bounded number of operations. + * *

Whereas the unbounded overload is appropriate for calling from cancelable, long-running tasks such as build steps, * this overload should be used when an answer is needed quickly, such as for {@link #validateFileMask(String)} * or anything else returning {@link FormValidation}. + * *

If a positive match is found, {@code null} is returned immediately. * A message is returned in case the file pattern can definitely be determined to not match anything in the directory within the alloted time. * If the time runs out without finding a match but without ruling out the possibility that there might be one, {@link InterruptedException} is thrown, * in which case the calling code should give the user the benefit of the doubt and use {@link hudson.util.FormValidation.Kind#OK} (with or without a message). + * + *

While this can be used in conjunction with {@link FormValidation}, it's generally better to use {@link #validateFileMask(String)} and + * its overloads for use in {@code doCheck} form validation methods related to workspaces, as that performs an appropriate permission check. + * Callers of this method or its overloads from web methods should ensure permissions are checked before this method is invoked. + * * @param bound a maximum number of negative operations (deliberately left vague) to perform before giving up on a precise answer; try {@link #VALIDATE_ANT_FILE_MASK_BOUND} * @throws InterruptedException not only in case of a channel failure, but also if too many operations were performed without finding any matches * @since 1.484 @@ -2936,7 +2952,7 @@ public String validateAntFileMask(final String fileMasks, final int bound) throw public @CheckForNull String validateAntFileMask(final String fileMasks, final int bound, final boolean caseSensitive) throws IOException, InterruptedException { return act(new ValidateAntFileMask(fileMasks, caseSensitive, bound)); } - private class ValidateAntFileMask extends MasterToSlaveFileCallable { + private static class ValidateAntFileMask extends MasterToSlaveFileCallable { private final String fileMasks; private final boolean caseSensitive; private final int bound; @@ -3150,8 +3166,13 @@ public FormValidation validateFileMask(String value, boolean errorIfNotExist) th /** * Checks the GLOB-style file mask. See {@link #validateAntFileMask(String)}. - * Requires configure permission on ancestor AbstractProject object in request, - * or admin permission if no such ancestor is found. + * Requires configure permission on ancestor {@link AbstractProject} object in request, + * or {@link Jenkins#MANAGE} permission if no such ancestor is found. + * + *

Note that this permission check may not always make sense based on the directory provided; + * callers should consider using {@link #validateFileMask(FilePath, String, boolean)} and its overloads instead + * (once appropriate permission checks have succeeded). + * * @since 1.294 */ public FormValidation validateFileMask(String value, boolean errorIfNotExist, boolean caseSensitive) throws IOException { @@ -3175,8 +3196,12 @@ public FormValidation validateFileMask(String value, boolean errorIfNotExist, bo /** * Validates a relative file path from this {@link FilePath}. - * Requires configure permission on ancestor AbstractProject object in request, - * or admin permission if no such ancestor is found. + * Requires configure permission on ancestor {@link AbstractProject} object in request, + * or {@link Jenkins#MANAGE} permission if no such ancestor is found. + * + *

Note that this permission check may not always make sense based on the directory provided; + * callers should consider using {@link #validateFileMask(FilePath, String, boolean)} and its overloads instead + * (once appropriate permission checks have succeeded). * * @param value * The relative path being validated. @@ -3298,7 +3323,7 @@ private void readObject(ObjectInputStream ois) throws IOException, ClassNotFound private static final long serialVersionUID = 1L; - @SuppressFBWarnings("MS_SHOULD_BE_FINAL") + @SuppressFBWarnings(value = "MS_SHOULD_BE_FINAL", justification = "TODO needs triage") public static int SIDE_BUFFER_SIZE = 1024; private static final Logger LOGGER = Logger.getLogger(FilePath.class.getName()); diff --git a/core/src/main/java/hudson/Functions.java b/core/src/main/java/hudson/Functions.java index 2242112cb6a0..2d54d3358f92 100644 --- a/core/src/main/java/hudson/Functions.java +++ b/core/src/main/java/hudson/Functions.java @@ -187,7 +187,7 @@ public class Functions { private static Logger LOGGER = Logger.getLogger(Functions.class.getName()); @Restricted(NoExternalUse.class) - @SuppressFBWarnings("MS_SHOULD_BE_FINAL") + @SuppressFBWarnings(value = "MS_SHOULD_BE_FINAL", justification = "for script console") public static /* non-final */ boolean UI_REFRESH = SystemProperties.getBoolean("jenkins.ui.refresh"); public Functions() { @@ -636,7 +636,7 @@ public static String getYuiSuffix() { /** * Set to true if you need to use the debug version of YUI. */ - @SuppressFBWarnings("MS_SHOULD_BE_FINAL") + @SuppressFBWarnings(value = "MS_SHOULD_BE_FINAL", justification = "for script console") public static boolean DEBUG_YUI = SystemProperties.getBoolean("debug.YUI"); /** diff --git a/core/src/main/java/hudson/Launcher.java b/core/src/main/java/hudson/Launcher.java index 85fc42ea5b9e..57de28f89d5e 100644 --- a/core/src/main/java/hudson/Launcher.java +++ b/core/src/main/java/hudson/Launcher.java @@ -27,6 +27,7 @@ import edu.umd.cs.findbugs.annotations.CheckForNull; import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import hudson.Proc.LocalProc; import hudson.Proc.ProcWithJenkins23271Patch; import hudson.model.Computer; @@ -1504,6 +1505,7 @@ private static EnvVars inherit(@NonNull Map overrides) { /** * Debug option to display full current path instead of just the last token. */ + @SuppressFBWarnings(value = "MS_SHOULD_BE_FINAL", justification = "for debugging") public static boolean showFullPath = false; private static final NullInputStream NULL_INPUT_STREAM = new NullInputStream(0); diff --git a/core/src/main/java/hudson/Main.java b/core/src/main/java/hudson/Main.java index 9c2cdc62c713..8ae8dfcd76ed 100644 --- a/core/src/main/java/hudson/Main.java +++ b/core/src/main/java/hudson/Main.java @@ -24,6 +24,7 @@ package hudson; import com.thoughtworks.xstream.core.util.Base64Encoder; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import hudson.util.DualOutputStream; import hudson.util.EncodingStream; import hudson.util.IOUtils; @@ -199,7 +200,7 @@ public static int remotePost(String[] args) throws Exception { } } } finally { - tmpFile.delete(); + Files.delete(Util.fileToPath(tmpFile)); } } @@ -216,12 +217,14 @@ private static HttpURLConnection open(URL url) throws IOException { /** * Set to true if we are running unit tests. */ + @SuppressFBWarnings(value = "MS_SHOULD_BE_FINAL", justification = "for debugging") public static boolean isUnitTest = false; /** * Set to true if we are running inside {@code mvn jetty:run}. * This is also set if running inside {@code mvn hpi:run} since plugins parent POM 2.30. */ + @SuppressFBWarnings(value = "MS_SHOULD_BE_FINAL", justification = "for debugging") public static boolean isDevelopmentMode = SystemProperties.getBoolean(Main.class.getName()+".development"); /** diff --git a/core/src/main/java/hudson/Plugin.java b/core/src/main/java/hudson/Plugin.java index 87ab160949e7..0942db8fd90d 100644 --- a/core/src/main/java/hudson/Plugin.java +++ b/core/src/main/java/hudson/Plugin.java @@ -310,7 +310,7 @@ public Object getTarget() { * Escape hatch for StaplerProxy-based access control */ @Restricted(NoExternalUse.class) - @SuppressFBWarnings("MS_SHOULD_BE_FINAL") + @SuppressFBWarnings(value = "MS_SHOULD_BE_FINAL", justification = "for script console") public static /* Script Console modifiable */ boolean SKIP_PERMISSION_CHECK = SystemProperties.getBoolean(Plugin.class.getName() + ".skipPermissionCheck"); /** diff --git a/core/src/main/java/hudson/PluginFirstClassLoader2.java b/core/src/main/java/hudson/PluginFirstClassLoader2.java new file mode 100644 index 000000000000..974939d53acb --- /dev/null +++ b/core/src/main/java/hudson/PluginFirstClassLoader2.java @@ -0,0 +1,135 @@ +package hudson; + +import edu.umd.cs.findbugs.annotations.NonNull; +import hudson.util.CompoundEnumeration; +import java.io.IOException; +import java.net.URL; +import java.util.Enumeration; +import java.util.Objects; +import jenkins.util.URLClassLoader2; +import org.kohsuke.accmod.Restricted; +import org.kohsuke.accmod.restrictions.NoExternalUse; + +/** + * Class loader that consults the plugin's {@code WEB-INF/lib/*.jar} and {@code WEB-INF/classes} + * directories and the Jenkins core class loader (in that order). + * + *

To use this class loader, set {@code pluginFirstClassLoader} to {@code true} in the {@code + * maven-hpi-plugin} configuration. + * + * @author Basil Crow + */ +@Restricted(NoExternalUse.class) +public class PluginFirstClassLoader2 extends URLClassLoader2 { + static { + registerAsParallelCapable(); + } + + public PluginFirstClassLoader2(@NonNull URL[] urls, @NonNull ClassLoader parent) { + super(Objects.requireNonNull(urls), Objects.requireNonNull(parent)); + } + + /** + * Load the class with the specified binary name. This method searches for classes in the + * following order: + * + *

    + *
  1. + *

    Invoke {@link #findLoadedClass(String)} to check if the class has already been + * loaded. + *

  2. + *

    Invoke {@link #findClass(String)} to find the class. + *

  3. + *

    Invoke {@link #loadClass(String)} on the parent class loader. + *

+ * + *

If the class was found using the above steps and the {@code resolve} flag is true, this + * method will then invoke {@link #resolveClass(Class)} on the resulting {@link Class} object. + * + *

This method synchronizes on the result of {@link #getClassLoadingLock(String)} during the + * entire class loading process. + * + * @param name The binary name of the class + * @param resolve If {@code true} then resolve the class + * @return The resulting {@link Class} object + * @throws ClassNotFoundException If the class could not be found + */ + @Override + protected Class loadClass(String name, boolean resolve) throws ClassNotFoundException { + synchronized (getClassLoadingLock(name)) { + Class c = findLoadedClass(name); + if (c == null) { + try { + c = findClass(name); + } catch (ClassNotFoundException e) { + // ignore + } + } + if (c == null) { + c = getParent().loadClass(name); + } + if (resolve) { + resolveClass(c); + } + return c; + } + } + + /** + * Find the resource with the given name. A resource is some data (images, audio, text, etc) + * that can be accessed by class code in a way that is independent of the location of the code. + * + *

The name of a resource is a '{@code /}'-separated path name that identifies the resource. + * This method searches for resources in the following order: + * + *

    + *
  1. + *

    Invoke {@link #findResource(String)} to find the resource. + *

  2. + *

    Invoke {@link #getResource(String)} on the parent class loader. + *

+ * + * @param name The resource name + * @return {@link URL} object for reading the resource; {@code null} if the resource could not + * be found, a {@link URL} could not be constructed to locate the resource, the resource is + * in a package that is not opened unconditionally, or access to the resource is denied by + * the security manager. + * @throws NullPointerException If {@code name} is {@code null} + */ + @Override + public URL getResource(String name) { + Objects.requireNonNull(name); + URL url = findResource(name); + if (url == null) { + url = getParent().getResource(name); + } + return url; + } + + /** + * Find all the resources with the given name. A resource is some data (images, audio, text, + * etc) that can be accessed by class code in a way that is independent of the location of the + * code. + * + *

The name of a resource is a {@code /}-separated path name that identifies the resource. + * This method first invokes {@link #findResources(String)} to find the resources with the name + * in this class loader. Finally, it invokes {@link #getResources(String)} on the parent class + * loader. It returns an enumeration whose elements are the {@link URL}s found by searching the + * {@link URL}s found with {@link #findResources(String)}, followed by the {@link URL}s found by + * searching the parent class loader. + * + * @param name The resource name + * @return An enumeration of {@link URL} objects for the resource. If no resources could be + * found, the enumeration will be empty. Resources for which a {@link URL} cannot be + * constructed, which are in a package that is not opened unconditionally, or for which + * access to the resource is denied by the security manager, are not returned in the + * enumeration. + * @throws IOException If I/O errors occur + * @throws NullPointerException If {@code name} is {@code null} + */ + @Override + public Enumeration getResources(String name) throws IOException { + Objects.requireNonNull(name); + return new CompoundEnumeration<>(findResources(name), getParent().getResources(name)); + } +} diff --git a/core/src/main/java/hudson/PluginManager.java b/core/src/main/java/hudson/PluginManager.java index e45bc76ba1bd..3dbb793b19c9 100644 --- a/core/src/main/java/hudson/PluginManager.java +++ b/core/src/main/java/hudson/PluginManager.java @@ -69,6 +69,7 @@ import java.io.FilenameFilter; import java.io.IOException; import java.io.InputStream; +import java.io.UncheckedIOException; import java.lang.reflect.Method; import java.net.JarURLConnection; import java.net.MalformedURLException; @@ -77,6 +78,7 @@ import java.net.URLClassLoader; import java.net.URLConnection; import java.nio.file.Files; +import java.nio.file.attribute.FileTime; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; @@ -88,6 +90,7 @@ import java.util.List; import java.util.Locale; import java.util.Map; +import java.util.Objects; import java.util.Optional; import java.util.ServiceLoader; import java.util.Set; @@ -203,7 +206,7 @@ public abstract class PluginManager extends AbstractModelObject implements OnMas // Secure initialization CHECK_UPDATE_SLEEP_TIME_MILLIS = SystemProperties.getInteger(PluginManager.class.getName() + ".checkUpdateSleepTimeMillis", 1000); CHECK_UPDATE_ATTEMPTS = SystemProperties.getInteger(PluginManager.class.getName() + ".checkUpdateAttempts", 1); - } catch(Exception e) { + } catch(RuntimeException e) { LOGGER.warning(String.format("There was an error initializing the PluginManager. Exception: %s", e)); } finally { CHECK_UPDATE_ATTEMPTS = CHECK_UPDATE_ATTEMPTS > 0 ? CHECK_UPDATE_ATTEMPTS : 1; @@ -271,10 +274,6 @@ PluginManager doCreate(@NonNull Class klass, } } LOGGER.log(WARNING, String.format("Provided custom plugin manager [%s] does not provide any of the suitable constructors. Using default.", pmClassName)); - } catch(NullPointerException e) { - // Class.forName and Class.getConstructor are supposed to never return null though a broken ClassLoader - // could break the contract. Just in case we introduce this specific catch to avoid polluting the logs with NPEs. - LOGGER.log(WARNING, String.format("Unable to instantiate custom plugin manager [%s]. Using default.", pmClassName)); } catch(ClassCastException e) { LOGGER.log(WARNING, String.format("Provided class [%s] does not extend PluginManager. Using default.", pmClassName)); } catch(Exception e) { @@ -355,8 +354,11 @@ protected PluginManager(ServletContext context, File rootDir) { this.context = context; this.rootDir = rootDir; - if(!rootDir.exists()) - rootDir.mkdirs(); + try { + Files.createDirectories(rootDir.toPath()); + } catch (IOException e) { + throw new UncheckedIOException(e); + } String workDir = SystemProperties.getString(PluginManager.class.getName()+".workDir"); this.workDir = StringUtils.isBlank(workDir) ? null : new File(workDir); @@ -541,11 +543,13 @@ public void run(Reactor session) throws Exception { failedPlugins.add(new FailedPlugin(p, e)); activePlugins.remove(p); plugins.remove(p); + p.releaseClassLoader(); LOGGER.log(Level.SEVERE, "Failed to install {0}: {1}", new Object[] { p.getShortName(), e.getMessage() }); } catch (IOException e) { failedPlugins.add(new FailedPlugin(p, e)); activePlugins.remove(p); plugins.remove(p); + p.releaseClassLoader(); throw e; } } @@ -566,6 +570,7 @@ public void run(Reactor session) throws Exception { failedPlugins.add(new FailedPlugin(p, e)); activePlugins.remove(p); plugins.remove(p); + p.releaseClassLoader(); throw e; } } @@ -647,7 +652,7 @@ void considerDetachedPlugin(String shortName) { } names.add(fileName); - copyBundledPlugin(url, fileName); + copyBundledPlugin(Objects.requireNonNull(url), fileName); copiedPlugins.add(url); try { addDependencies(url, fromPath, dependencies); @@ -910,9 +915,7 @@ public void dynamicLoad(File arc, boolean removeExisting, @CheckForNull List pluginsCopy = new ArrayList<>(plugins); + for (PluginWrapper p : pluginsCopy) { + activePlugins.remove(p); + plugins.remove(p); p.releaseClassLoader(); } - activePlugins.clear(); // Work around a bug in commons-logging. // See http://www.szegedi.org/articles/memleak.html LogFactory.release(uberClassLoader); @@ -1589,7 +1597,7 @@ public HttpResponse doInstallPlugins(StaplerRequest req) throws IOException { responseData.put("correlationId", correlationId.toString()); return hudson.util.HttpResponses.okJSON(responseData); - } catch (Exception e) { + } catch (RuntimeException e) { return hudson.util.HttpResponses.errorJSON(e.getMessage()); } } @@ -1743,7 +1751,7 @@ interface PluginCopier { void cleanup(); } - class FileUploadPluginCopier implements PluginCopier { + static class FileUploadPluginCopier implements PluginCopier { private FileItem fileItem; FileUploadPluginCopier(FileItem fileItem) { @@ -1761,7 +1769,7 @@ public void cleanup() { } } - class UrlPluginCopier implements PluginCopier { + static class UrlPluginCopier implements PluginCopier { private String url; UrlPluginCopier(String url) { @@ -1817,10 +1825,9 @@ public HttpResponse doUploadPlugin(StaplerRequest req) throws IOException, Servl // first copy into a temporary file name File t = File.createTempFile("uploaded", ".jpi"); t.deleteOnExit(); + // TODO Remove this workaround after FILEUPLOAD-293 is resolved. + Files.delete(Util.fileToPath(t)); try { - // TODO Remove this workaround after FILEUPLOAD-293 is resolved. - t.delete(); - copier.copy(t); } catch (Exception e) { // Exception thrown is too generic so at least limit the scope where it can occur @@ -2248,7 +2255,7 @@ public String toString() { return "classLoader " + getClass().getName(); } } - @SuppressFBWarnings("MS_SHOULD_BE_FINAL") + @SuppressFBWarnings(value = "MS_SHOULD_BE_FINAL", justification = "for script console") public static boolean FAST_LOOKUP = !SystemProperties.getBoolean(PluginManager.class.getName()+".noFastLookup"); /** @deprecated in Jenkins 2.222 use {@link Jenkins#ADMINISTER} instead */ @@ -2471,6 +2478,6 @@ public boolean hasAdoptThisPluginLabel(PluginWrapper plugin) { * Escape hatch for StaplerProxy-based access control */ @Restricted(NoExternalUse.class) - @SuppressFBWarnings("MS_SHOULD_BE_FINAL") + @SuppressFBWarnings(value = "MS_SHOULD_BE_FINAL", justification = "for script console") public static /* Script Console modifiable */ boolean SKIP_PERMISSION_CHECK = SystemProperties.getBoolean(PluginManager.class.getName() + ".skipPermissionCheck"); } diff --git a/core/src/main/java/hudson/PluginWrapper.java b/core/src/main/java/hudson/PluginWrapper.java index adbb4a6f3a1e..d2ed43ec2e28 100644 --- a/core/src/main/java/hudson/PluginWrapper.java +++ b/core/src/main/java/hudson/PluginWrapper.java @@ -1378,7 +1378,7 @@ public HttpResponse doDoUninstall() throws IOException { Jenkins jenkins = Jenkins.get(); jenkins.checkPermission(Jenkins.ADMINISTER); - archive.delete(); + Files.deleteIfExists(Util.fileToPath(archive)); // Redo who depends on who. jenkins.getPluginManager().resolveDependentPlugins(); diff --git a/core/src/main/java/hudson/Proc.java b/core/src/main/java/hudson/Proc.java index 0f8f5a47f4ca..dd94d2097c39 100644 --- a/core/src/main/java/hudson/Proc.java +++ b/core/src/main/java/hudson/Proc.java @@ -503,7 +503,7 @@ public OutputStream getStdin() { /** * Debug switch to have the thread display the process it's waiting for. */ - @SuppressFBWarnings("MS_SHOULD_BE_FINAL") + @SuppressFBWarnings(value = "MS_SHOULD_BE_FINAL", justification = "for debugging") public static boolean SHOW_PID = false; /** diff --git a/core/src/main/java/hudson/TcpSlaveAgentListener.java b/core/src/main/java/hudson/TcpSlaveAgentListener.java index e7875e95bdb0..717c1df91953 100644 --- a/core/src/main/java/hudson/TcpSlaveAgentListener.java +++ b/core/src/main/java/hudson/TcpSlaveAgentListener.java @@ -134,7 +134,7 @@ public String getAdvertisedHost() { } try { return new URL(Jenkins.get().getRootUrl()).getHost(); - } catch (MalformedURLException | NullPointerException e) { + } catch (MalformedURLException e) { throw new IllegalStateException("Could not get TcpSlaveAgentListener host name", e); } } diff --git a/core/src/main/java/hudson/Util.java b/core/src/main/java/hudson/Util.java index d0f004f86870..32d114efb20f 100644 --- a/core/src/main/java/hudson/Util.java +++ b/core/src/main/java/hudson/Util.java @@ -460,7 +460,7 @@ public static String getWin32ErrorMessage(Throwable e) { try { ResourceBundle rb = ResourceBundle.getBundle("/hudson/win32errors"); return rb.getString("error"+m.group(1)); - } catch (Exception ignored) { + } catch (RuntimeException ignored) { // silently recover from resource related failures } } @@ -1070,7 +1070,7 @@ public static void touch(@NonNull File file) throws IOException { */ public static void copyFile(@NonNull File src, @NonNull File dst) throws BuildException { Copy cp = new Copy(); - cp.setProject(new org.apache.tools.ant.Project()); + cp.setProject(new Project()); cp.setTofile(dst); cp.setFile(src); cp.setOverwrite(true); @@ -1429,7 +1429,7 @@ public static String resolveSymlink(@NonNull File link) throws IOException { return null; } catch (IOException x) { throw x; - } catch (Exception x) { + } catch (RuntimeException x) { throw new IOException(x); } } diff --git a/core/src/main/java/hudson/WebAppMain.java b/core/src/main/java/hudson/WebAppMain.java index c7fdcced0234..f41de96eac8d 100644 --- a/core/src/main/java/hudson/WebAppMain.java +++ b/core/src/main/java/hudson/WebAppMain.java @@ -189,13 +189,13 @@ public Locale get() { final FileAndDescription describedHomeDir = getHomeDir(event); home = describedHomeDir.file.getAbsoluteFile(); - home.mkdirs(); + try { + Files.createDirectories(home.toPath()); + } catch (IOException | InvalidPathException e) { + throw (NoHomeDir)new NoHomeDir(home).initCause(e); + } LOGGER.info("Jenkins home directory: "+ home +" found at: " + describedHomeDir.description); - // check that home exists (as mkdirs could have failed silently), otherwise throw a meaningful error - if (!home.exists()) - throw new NoHomeDir(home); - recordBootAttempt(home); // make sure that we are using XStream in the "enhanced" (JVM-specific) mode @@ -261,6 +261,7 @@ public Locale get() { final File _home = home; initThread = new Thread("Jenkins initialization thread") { + @SuppressFBWarnings(value = "RV_RETURN_VALUE_IGNORED_BAD_PRACTICE", justification = "TODO needs triage") @Override public void run() { boolean success = false; @@ -324,7 +325,7 @@ public static void installExpressionFactory(ServletContextEvent event) { /** * Installs log handler to monitor all Hudson logs. */ - @SuppressFBWarnings("LG_LOST_LOGGER_DUE_TO_WEAK_REFERENCE") + @SuppressFBWarnings(value = "LG_LOST_LOGGER_DUE_TO_WEAK_REFERENCE", justification = "TODO needs triage") private void installLogger() { Jenkins.logRecords = handler.getView(); Logger.getLogger("").addHandler(handler); @@ -414,7 +415,7 @@ public void contextDestroyed(ServletContextEvent event) { if (instance != null) { instance.cleanUp(); } - } catch (Exception e) { + } catch (Throwable e) { LOGGER.log(Level.SEVERE, "Failed to clean up. Restart will continue.", e); } diff --git a/core/src/main/java/hudson/XmlFile.java b/core/src/main/java/hudson/XmlFile.java index 75bff5fc0f78..9a36d4484d20 100644 --- a/core/src/main/java/hudson/XmlFile.java +++ b/core/src/main/java/hudson/XmlFile.java @@ -246,12 +246,12 @@ public boolean exists() { return file.exists(); } - public void delete() { - file.delete(); + public void delete() throws IOException { + Files.deleteIfExists(Util.fileToPath(file)); } - public void mkdirs() { - file.getParentFile().mkdirs(); + public void mkdirs() throws IOException { + Files.createDirectories(Util.fileToPath(file.getParentFile())); } @Override diff --git a/core/src/main/java/hudson/cli/BuildCommand.java b/core/src/main/java/hudson/cli/BuildCommand.java index 1902ac5df6b6..bb84871a7f1c 100644 --- a/core/src/main/java/hudson/cli/BuildCommand.java +++ b/core/src/main/java/hudson/cli/BuildCommand.java @@ -49,6 +49,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Objects; import jenkins.model.Jenkins; import jenkins.model.ParameterizedJobMixIn; import jenkins.scm.SCMDecisionHandler; @@ -262,12 +263,22 @@ public void print(TaskListener listener) { @Override public boolean equals(Object o) { - return o instanceof CLICause; + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + if (!super.equals(o)) { + return false; + } + CLICause cliCause = (CLICause) o; + return Objects.equals(startedBy, cliCause.startedBy); } @Override public int hashCode() { - return 7; + return Objects.hash(super.hashCode(), startedBy); } } } diff --git a/core/src/main/java/hudson/cli/DisablePluginCommand.java b/core/src/main/java/hudson/cli/DisablePluginCommand.java index 59a1402a89e4..79d3a09f8250 100644 --- a/core/src/main/java/hudson/cli/DisablePluginCommand.java +++ b/core/src/main/java/hudson/cli/DisablePluginCommand.java @@ -82,7 +82,15 @@ protected int run() throws Exception { try { strategyToUse = PluginWrapper.PluginDisableStrategy.valueOf(strategy.toUpperCase()); } catch (IllegalArgumentException iae) { - throw new IllegalArgumentException(hudson.cli.Messages.DisablePluginCommand_NoSuchStrategy(strategy, String.format("%s, %s, %s", PluginWrapper.PluginDisableStrategy.NONE, PluginWrapper.PluginDisableStrategy.MANDATORY, PluginWrapper.PluginDisableStrategy.ALL)), iae); + throw new IllegalArgumentException( + hudson.cli.Messages.DisablePluginCommand_NoSuchStrategy( + strategy, + String.format( + "%s, %s, %s", + PluginWrapper.PluginDisableStrategy.NONE, + PluginWrapper.PluginDisableStrategy.MANDATORY, + PluginWrapper.PluginDisableStrategy.ALL)), + iae); } // disable... diff --git a/core/src/main/java/hudson/console/ConsoleNote.java b/core/src/main/java/hudson/console/ConsoleNote.java index 1086fb8b3c30..443625350cb2 100644 --- a/core/src/main/java/hudson/console/ConsoleNote.java +++ b/core/src/main/java/hudson/console/ConsoleNote.java @@ -144,7 +144,7 @@ public abstract class ConsoleNote implements Serializable, Describable pools = new ArrayList<>(); /** diff --git a/core/src/main/java/hudson/init/impl/InitialUserContent.java b/core/src/main/java/hudson/init/impl/InitialUserContent.java index 79d02881a110..e057ba16a3f7 100644 --- a/core/src/main/java/hudson/init/impl/InitialUserContent.java +++ b/core/src/main/java/hudson/init/impl/InitialUserContent.java @@ -25,10 +25,12 @@ import static hudson.init.InitMilestone.JOB_CONFIG_ADAPTED; +import hudson.Util; import hudson.init.Initializer; import hudson.model.Messages; import java.io.File; import java.io.IOException; +import java.nio.file.Files; import jenkins.model.Jenkins; import org.apache.commons.io.FileUtils; @@ -40,8 +42,8 @@ public class InitialUserContent { @Initializer(after=JOB_CONFIG_ADAPTED) public static void init(Jenkins h) throws IOException { File userContentDir = new File(h.getRootDir(), "userContent"); - if(!userContentDir.exists()) { - userContentDir.mkdirs(); + if (!Files.isDirectory(Util.fileToPath(userContentDir))) { + Files.createDirectories(Util.fileToPath(userContentDir)); FileUtils.writeStringToFile(new File(userContentDir,"readme.txt"), Messages.Hudson_USER_CONTENT_README() + "\n"); } } diff --git a/core/src/main/java/hudson/lifecycle/ExitLifecycle.java b/core/src/main/java/hudson/lifecycle/ExitLifecycle.java index 47b093d43f46..01855e1e225e 100644 --- a/core/src/main/java/hudson/lifecycle/ExitLifecycle.java +++ b/core/src/main/java/hudson/lifecycle/ExitLifecycle.java @@ -65,7 +65,7 @@ public void restart() { if (jenkins != null) { jenkins.cleanUp(); } - } catch (Exception e) { + } catch (Throwable e) { LOGGER.log(Level.SEVERE, "Failed to clean up. Restart will continue.", e); } diff --git a/core/src/main/java/hudson/lifecycle/Lifecycle.java b/core/src/main/java/hudson/lifecycle/Lifecycle.java index 560913098e22..04123c797ef0 100644 --- a/core/src/main/java/hudson/lifecycle/Lifecycle.java +++ b/core/src/main/java/hudson/lifecycle/Lifecycle.java @@ -30,6 +30,7 @@ import java.io.IOException; import java.io.UncheckedIOException; import java.lang.reflect.InvocationTargetException; +import java.nio.file.Files; import java.util.logging.Level; import java.util.logging.Logger; import jenkins.model.Jenkins; @@ -166,8 +167,9 @@ public void rewriteHudsonWar(File by) throws IOException { FileUtils.copyFile(by, dest); // we don't want to keep backup if we are downgrading - if (by.equals(bak)&&bak.exists()) - bak.delete(); + if (by.equals(bak)) { + Files.deleteIfExists(Util.fileToPath(bak)); + } } /** diff --git a/core/src/main/java/hudson/lifecycle/SolarisSMFLifecycle.java b/core/src/main/java/hudson/lifecycle/SolarisSMFLifecycle.java index 8a10858b0e1f..eecc28ea26de 100644 --- a/core/src/main/java/hudson/lifecycle/SolarisSMFLifecycle.java +++ b/core/src/main/java/hudson/lifecycle/SolarisSMFLifecycle.java @@ -46,7 +46,7 @@ public void restart() throws IOException, InterruptedException { if (jenkins != null) { jenkins.cleanUp(); } - } catch (Exception e) { + } catch (Throwable e) { LOGGER.log(Level.SEVERE, "Failed to clean up. Restart will continue.", e); } System.exit(0); diff --git a/core/src/main/java/hudson/lifecycle/UnixLifecycle.java b/core/src/main/java/hudson/lifecycle/UnixLifecycle.java index 391998cdffce..498db2c0e984 100644 --- a/core/src/main/java/hudson/lifecycle/UnixLifecycle.java +++ b/core/src/main/java/hudson/lifecycle/UnixLifecycle.java @@ -68,7 +68,7 @@ public void restart() throws IOException, InterruptedException { if (jenkins != null) { jenkins.cleanUp(); } - } catch (Exception e) { + } catch (Throwable e) { LOGGER.log(Level.SEVERE, "Failed to clean up. Restart will continue.", e); } diff --git a/core/src/main/java/hudson/lifecycle/WindowsInstallerLink.java b/core/src/main/java/hudson/lifecycle/WindowsInstallerLink.java index e5772b8ff8d7..367b25230821 100644 --- a/core/src/main/java/hudson/lifecycle/WindowsInstallerLink.java +++ b/core/src/main/java/hudson/lifecycle/WindowsInstallerLink.java @@ -29,6 +29,7 @@ import hudson.Extension; import hudson.Functions; import hudson.Launcher.LocalLauncher; +import hudson.Util; import hudson.model.ManagementLink; import hudson.model.TaskListener; import hudson.util.StreamTaskListener; @@ -37,6 +38,7 @@ import java.io.IOException; import java.net.URL; import java.nio.charset.Charset; +import java.nio.file.Files; import java.util.logging.Level; import java.util.logging.Logger; import javax.servlet.ServletException; @@ -137,7 +139,7 @@ public void doDoInstall(StaplerRequest req, StaplerResponse rsp, @QueryParameter try { // copy files over there copy(req, rsp, dir, getClass().getResource("/windows-service/jenkins.exe"), "jenkins.exe"); - new File(dir, "jenkins.exe.config").delete(); + Files.deleteIfExists(Util.fileToPath(dir).resolve("jenkins.exe.config")); copy(req, rsp, dir, getClass().getResource("/windows-service/jenkins.xml"), "jenkins.xml"); if(!hudsonWar.getCanonicalFile().equals(new File(dir,"jenkins.war").getCanonicalFile())) copy(req, rsp, dir, hudsonWar.toURI().toURL(), "jenkins.war"); diff --git a/core/src/main/java/hudson/lifecycle/WindowsServiceLifecycle.java b/core/src/main/java/hudson/lifecycle/WindowsServiceLifecycle.java index 01d840f9dd50..8612f2cf0d6f 100644 --- a/core/src/main/java/hudson/lifecycle/WindowsServiceLifecycle.java +++ b/core/src/main/java/hudson/lifecycle/WindowsServiceLifecycle.java @@ -121,7 +121,7 @@ public void restart() throws IOException, InterruptedException { if (jenkins != null) { jenkins.cleanUp(); } - } catch (Exception e) { + } catch (Throwable e) { LOGGER.log(Level.SEVERE, "Failed to clean up. Restart will continue.", e); } diff --git a/core/src/main/java/hudson/logging/LogRecorderManager.java b/core/src/main/java/hudson/logging/LogRecorderManager.java index cc1948d2a5d7..3d7164a2dcd9 100644 --- a/core/src/main/java/hudson/logging/LogRecorderManager.java +++ b/core/src/main/java/hudson/logging/LogRecorderManager.java @@ -170,7 +170,7 @@ public ContextMenu doChildrenContextMenu(StaplerRequest request, StaplerResponse /** * Configure the logging level. */ - @edu.umd.cs.findbugs.annotations.SuppressFBWarnings("LG_LOST_LOGGER_DUE_TO_WEAK_REFERENCE") + @SuppressFBWarnings(value = "LG_LOST_LOGGER_DUE_TO_WEAK_REFERENCE", justification = "TODO needs triage") @RequirePOST public HttpResponse doConfigLogger(@QueryParameter String name, @QueryParameter String level) { Jenkins.get().checkPermission(Jenkins.ADMINISTER); @@ -261,6 +261,6 @@ public Object getTarget() { * Escape hatch for StaplerProxy-based access control */ @Restricted(NoExternalUse.class) - @SuppressFBWarnings("MS_SHOULD_BE_FINAL") + @SuppressFBWarnings(value = "MS_SHOULD_BE_FINAL", justification = "for script console") public static /* Script Console modifiable */ boolean SKIP_PERMISSION_CHECK = SystemProperties.getBoolean(LogRecorderManager.class.getName() + ".skipPermissionCheck"); } diff --git a/core/src/main/java/hudson/model/AbstractCIBase.java b/core/src/main/java/hudson/model/AbstractCIBase.java index 05d4f6fcd382..13fb0b563580 100644 --- a/core/src/main/java/hudson/model/AbstractCIBase.java +++ b/core/src/main/java/hudson/model/AbstractCIBase.java @@ -41,13 +41,14 @@ import java.util.logging.Level; import java.util.logging.Logger; import jenkins.model.Jenkins; +import jenkins.util.Listeners; import jenkins.util.SystemProperties; import org.kohsuke.stapler.StaplerFallback; import org.kohsuke.stapler.StaplerProxy; public abstract class AbstractCIBase extends Node implements ItemGroup, StaplerProxy, StaplerFallback, ViewGroup, AccessControlled, DescriptorByNameOwner { - @SuppressFBWarnings("MS_SHOULD_BE_FINAL") + @SuppressFBWarnings(value = "MS_SHOULD_BE_FINAL", justification = "for script console") public static boolean LOG_STARTUP_PERFORMANCE = SystemProperties.getBoolean(Jenkins.class.getName() + "." + "logStartupPerformance", false); private static final Logger LOGGER = Logger.getLogger(AbstractCIBase.class.getName()); @@ -219,13 +220,7 @@ protected void updateNewComputer(final Node n, boolean automaticAgentLaunch) { } createNewComputerForNode(n, automaticAgentLaunch); getQueue().scheduleMaintenance(); - for (ComputerListener cl : ComputerListener.all()) { - try { - cl.onConfigurationChange(); - } catch (Throwable t) { - LOGGER.log(Level.WARNING, null, t); - } - } + Listeners.notify(ComputerListener.class, false, ComputerListener::onConfigurationChange); } /** @@ -278,13 +273,7 @@ public void run() { killComputer(c); } getQueue().scheduleMaintenance(); - for (ComputerListener cl : ComputerListener.all()) { - try { - cl.onConfigurationChange(); - } catch (Throwable t) { - LOGGER.log(Level.WARNING, null, t); - } - } + Listeners.notify(ComputerListener.class, false, ComputerListener::onConfigurationChange); } } diff --git a/core/src/main/java/hudson/model/AbstractItem.java b/core/src/main/java/hudson/model/AbstractItem.java index 329dc5167bcb..e16836c8a807 100644 --- a/core/src/main/java/hudson/model/AbstractItem.java +++ b/core/src/main/java/hudson/model/AbstractItem.java @@ -78,6 +78,7 @@ import jenkins.util.SystemProperties; import jenkins.util.xml.XMLUtils; import org.apache.commons.io.FileUtils; +import org.apache.tools.ant.Project; import org.apache.tools.ant.taskdefs.Copy; import org.apache.tools.ant.types.FileSet; import org.kohsuke.accmod.Restricted; @@ -418,7 +419,7 @@ protected void renameTo(final String newName) throws IOException { // shuts down there might be a new job created under the // old name. Copy cp = new Copy(); - cp.setProject(new org.apache.tools.ant.Project()); + cp.setProject(new Project()); cp.setTodir(newRoot); FileSet src = new FileSet(); src.setDir(oldRoot); @@ -967,7 +968,7 @@ public Object getTarget() { * Escape hatch for StaplerProxy-based access control */ @Restricted(NoExternalUse.class) - @SuppressFBWarnings("MS_SHOULD_BE_FINAL") + @SuppressFBWarnings(value = "MS_SHOULD_BE_FINAL", justification = "for script console") public static /* Script Console modifiable */ boolean SKIP_PERMISSION_CHECK = SystemProperties.getBoolean(AbstractItem.class.getName() + ".skipPermissionCheck"); /** diff --git a/core/src/main/java/hudson/model/AbstractProject.java b/core/src/main/java/hudson/model/AbstractProject.java index 1e080cb8e6b0..48666d0bad24 100644 --- a/core/src/main/java/hudson/model/AbstractProject.java +++ b/core/src/main/java/hudson/model/AbstractProject.java @@ -738,7 +738,7 @@ protected List createTransientActions() { for (TransientProjectActionFactory tpaf : TransientProjectActionFactory.all()) { try { ta.addAll(Util.fixNull(tpaf.createFor(this))); // be defensive against null - } catch (Exception e) { + } catch (RuntimeException e) { LOGGER.log(Level.SEVERE, "Could not load actions from " + tpaf + " for " + this, e); } } diff --git a/core/src/main/java/hudson/model/Actionable.java b/core/src/main/java/hudson/model/Actionable.java index a67eb41b8579..ff1456c9c741 100644 --- a/core/src/main/java/hudson/model/Actionable.java +++ b/core/src/main/java/hudson/model/Actionable.java @@ -25,7 +25,6 @@ import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; -import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import hudson.Util; import java.util.ArrayList; import java.util.Collection; @@ -119,7 +118,7 @@ private Collection createFor(TransientActionFactory taf } } return result; - } catch (Exception e) { + } catch (RuntimeException e) { LOGGER.log(Level.WARNING, "Could not load actions from " + taf + " for " + this, e); return Collections.emptySet(); } @@ -147,7 +146,6 @@ public List getActions(Class type) { * Note: this method will always modify the actions */ @SuppressWarnings("ConstantConditions") - @SuppressFBWarnings("RCN_REDUNDANT_NULLCHECK_OF_NONNULL_VALUE") public void addAction(@NonNull Action a) { if(a==null) { throw new IllegalArgumentException("Action must be non-null"); @@ -170,7 +168,6 @@ public void addAction(@NonNull Action a) { * example in cases where the caller would need to persist the {@link Actionable} in order to persist the change * and there is a desire to elide unnecessary persistence of unmodified objects. */ - @SuppressFBWarnings("RCN_REDUNDANT_NULLCHECK_OF_NONNULL_VALUE") public void replaceAction(@NonNull Action a) { addOrReplaceAction(a); } @@ -189,7 +186,6 @@ public void replaceAction(@NonNull Action a) { * @since 2.29 */ @SuppressWarnings("ConstantConditions") - @SuppressFBWarnings("RCN_REDUNDANT_NULLCHECK_OF_NONNULL_VALUE") public boolean addOrReplaceAction(@NonNull Action a) { if (a == null) { throw new IllegalArgumentException("Action must be non-null"); @@ -247,7 +243,6 @@ public boolean removeAction(@Nullable Action a) { * @since 2.29 */ @SuppressWarnings("ConstantConditions") - @SuppressFBWarnings("RCN_REDUNDANT_NULLCHECK_OF_NONNULL_VALUE") public boolean removeActions(@NonNull Class clazz) { if (clazz == null) { throw new IllegalArgumentException("Action type must be non-null"); @@ -279,7 +274,6 @@ public boolean removeActions(@NonNull Class clazz) { * @since 2.29 */ @SuppressWarnings("ConstantConditions") - @SuppressFBWarnings("RCN_REDUNDANT_NULLCHECK_OF_NONNULL_VALUE") public boolean replaceActions(@NonNull Class clazz, @NonNull Action a) { if (clazz == null) { throw new IllegalArgumentException("Action type must be non-null"); diff --git a/core/src/main/java/hudson/model/Computer.java b/core/src/main/java/hudson/model/Computer.java index cbcdb99cd30c..a7ac71edc807 100644 --- a/core/src/main/java/hudson/model/Computer.java +++ b/core/src/main/java/hudson/model/Computer.java @@ -52,7 +52,6 @@ import hudson.security.PermissionGroup; import hudson.security.PermissionScope; import hudson.slaves.AbstractCloudSlave; -import hudson.slaves.Cloud; import hudson.slaves.ComputerLauncher; import hudson.slaves.ComputerListener; import hudson.slaves.NodeProperty; @@ -78,6 +77,9 @@ import java.net.InetAddress; import java.net.NetworkInterface; import java.nio.charset.Charset; +import java.nio.file.Files; +import java.nio.file.InvalidPathException; +import java.nio.file.StandardCopyOption; import java.util.ArrayList; import java.util.Collections; import java.util.Date; @@ -103,6 +105,7 @@ import jenkins.security.MasterToSlaveCallable; import jenkins.security.stapler.StaplerDispatchable; import jenkins.util.ContextResettingExecutorService; +import jenkins.util.Listeners; import jenkins.util.SystemProperties; import net.jcip.annotations.GuardedBy; import org.apache.commons.lang.StringUtils; @@ -279,7 +282,6 @@ public List getActions() { } @SuppressWarnings({"ConstantConditions","deprecation"}) - @SuppressFBWarnings("RCN_REDUNDANT_NULLCHECK_OF_NONNULL_VALUE") @Override public void addAction(@NonNull Action a) { if (a == null) { @@ -707,9 +709,10 @@ public void setTemporarilyOffline(boolean temporarilyOffline, OfflineCause cause synchronized (statusChangeLock) { statusChangeLock.notifyAll(); } - for (ComputerListener cl : ComputerListener.all()) { - if (temporarilyOffline) cl.onTemporarilyOffline(this,cause); - else cl.onTemporarilyOnline(this); + if (temporarilyOffline) { + Listeners.notify(ComputerListener.class, false, l -> l.onTemporarilyOffline(this, cause)); + } else { + Listeners.notify(ComputerListener.class, false, l -> l.onTemporarilyOnline(this)); } } @@ -1605,7 +1608,7 @@ public Object getTarget() { * Escape hatch for StaplerProxy-based access control */ @Restricted(NoExternalUse.class) - @SuppressFBWarnings("MS_SHOULD_BE_FINAL") + @SuppressFBWarnings(value = "MS_SHOULD_BE_FINAL", justification = "for script console") public static /* Script Console modifiable */ boolean SKIP_PERMISSION_CHECK = SystemProperties.getBoolean(Computer.class.getName() + ".skipPermissionCheck"); /** @@ -1674,12 +1677,12 @@ public static void relocateOldLogs() { Matcher m = logfile.matcher(f.getName()); if (m.matches()) { File newLocation = new File(dir, "logs/slaves/" + m.group(1) + "/slave.log" + Util.fixNull(m.group(2))); - newLocation.getParentFile().mkdirs(); - boolean relocationSuccessful=f.renameTo(newLocation); - if (relocationSuccessful) { // The operation will fail if mkdir fails + try { + Files.createDirectories(newLocation.getParentFile().toPath()); + Files.move(f.toPath(), newLocation.toPath(), StandardCopyOption.REPLACE_EXISTING); LOGGER.log(Level.INFO, "Relocated log file {0} to {1}",new Object[] {f.getPath(),newLocation.getPath()}); - } else { - LOGGER.log(Level.WARNING, "Cannot relocate log file {0} to {1}",new Object[] {f.getPath(),newLocation.getPath()}); + } catch (IOException | InvalidPathException e) { + LOGGER.log(Level.WARNING, e, () -> "Cannot relocate log file " + f.getPath() + " to " + newLocation.getPath()); } } else { assert false; @@ -1787,25 +1790,63 @@ public long getWhen() { } public static final PermissionGroup PERMISSIONS = new PermissionGroup(Computer.class,Messages._Computer_Permissions_Title()); - public static final Permission CONFIGURE = new Permission(PERMISSIONS,"Configure", Messages._Computer_ConfigurePermission_Description(), Permission.CONFIGURE, PermissionScope.COMPUTER); + public static final Permission CONFIGURE = + new Permission( + PERMISSIONS, + "Configure", + Messages._Computer_ConfigurePermission_Description(), + Permission.CONFIGURE, + PermissionScope.COMPUTER); /** * @since 1.532 */ - public static final Permission EXTENDED_READ = new Permission(PERMISSIONS,"ExtendedRead", Messages._Computer_ExtendedReadPermission_Description(), CONFIGURE, SystemProperties.getBoolean("hudson.security.ExtendedReadPermission"), new PermissionScope[]{PermissionScope.COMPUTER}); - public static final Permission DELETE = new Permission(PERMISSIONS,"Delete", Messages._Computer_DeletePermission_Description(), Permission.DELETE, PermissionScope.COMPUTER); - public static final Permission CREATE = new Permission(PERMISSIONS,"Create", Messages._Computer_CreatePermission_Description(), Permission.CREATE, PermissionScope.JENKINS); - public static final Permission DISCONNECT = new Permission(PERMISSIONS,"Disconnect", Messages._Computer_DisconnectPermission_Description(), Jenkins.ADMINISTER, PermissionScope.COMPUTER); - public static final Permission CONNECT = new Permission(PERMISSIONS,"Connect", Messages._Computer_ConnectPermission_Description(), DISCONNECT, PermissionScope.COMPUTER); - public static final Permission BUILD = new Permission(PERMISSIONS, "Build", Messages._Computer_BuildPermission_Description(), Permission.WRITE, PermissionScope.COMPUTER); + public static final Permission EXTENDED_READ = + new Permission( + PERMISSIONS, + "ExtendedRead", + Messages._Computer_ExtendedReadPermission_Description(), + CONFIGURE, + SystemProperties.getBoolean("hudson.security.ExtendedReadPermission"), + new PermissionScope[] {PermissionScope.COMPUTER}); + public static final Permission DELETE = + new Permission( + PERMISSIONS, + "Delete", + Messages._Computer_DeletePermission_Description(), + Permission.DELETE, + PermissionScope.COMPUTER); + public static final Permission CREATE = + new Permission( + PERMISSIONS, + "Create", + Messages._Computer_CreatePermission_Description(), + Permission.CREATE, + PermissionScope.JENKINS); + public static final Permission DISCONNECT = + new Permission( + PERMISSIONS, + "Disconnect", + Messages._Computer_DisconnectPermission_Description(), + Jenkins.ADMINISTER, + PermissionScope.COMPUTER); + public static final Permission CONNECT = + new Permission( + PERMISSIONS, + "Connect", + Messages._Computer_ConnectPermission_Description(), + DISCONNECT, + PermissionScope.COMPUTER); + public static final Permission BUILD = + new Permission( + PERMISSIONS, + "Build", + Messages._Computer_BuildPermission_Description(), + Permission.WRITE, + PermissionScope.COMPUTER); @Restricted(NoExternalUse.class) // called by jelly public static final Permission[] EXTENDED_READ_AND_CONNECT = new Permission[] { EXTENDED_READ, CONNECT }; - // This permission was historically scoped to this class albeit declared in Cloud. While deserializing, Jenkins loads - // the scope class to make sure the permission is initialized and registered. since Cloud class is used rather seldom, - // it might appear the permission does not exist. Referencing the permission from here to make sure it gets loaded. - private static final @Deprecated Permission CLOUD_PROVISION = Cloud.PROVISION; - private static final Logger LOGGER = Logger.getLogger(Computer.class.getName()); } diff --git a/core/src/main/java/hudson/model/DownloadService.java b/core/src/main/java/hudson/model/DownloadService.java index 17985dca37a4..92713f0cf9b1 100644 --- a/core/src/main/java/hudson/model/DownloadService.java +++ b/core/src/main/java/hudson/model/DownloadService.java @@ -33,6 +33,7 @@ import hudson.ExtensionListListener; import hudson.ExtensionPoint; import hudson.ProxyConfiguration; +import hudson.Util; import hudson.init.InitMilestone; import hudson.init.Initializer; import hudson.util.FormValidation; @@ -46,6 +47,8 @@ import java.net.URLConnection; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.attribute.FileTime; import java.util.ArrayList; import java.util.List; import java.util.logging.Level; @@ -353,8 +356,13 @@ public JSONObject getData() throws IOException { try { return JSONObject.fromObject(df.read()); } catch (JSONException e) { - df.delete(); // if we keep this file, it will cause repeated failures - throw new IOException("Failed to parse "+df+" into JSON",e); + IOException ioe = new IOException("Failed to parse " + df + " into JSON", e); + try { + df.delete(); // if we keep this file, it will cause repeated failures + } catch (IOException e2) { + ioe.addSuppressed(e2); + } + throw ioe; } return null; } @@ -362,7 +370,7 @@ public JSONObject getData() throws IOException { private FormValidation load(String json, long dataTimestamp) throws IOException { TextFile df = getDataFile(); df.write(json); - df.file.setLastModified(dataTimestamp); + Files.setLastModifiedTime(Util.fileToPath(df.file), FileTime.fromMillis(dataTimestamp)); LOGGER.info("Obtained the updated data file for "+id); return FormValidation.ok(); } diff --git a/core/src/main/java/hudson/model/Executor.java b/core/src/main/java/hudson/model/Executor.java index 556f88d8e84e..cf00f7be5665 100644 --- a/core/src/main/java/hudson/model/Executor.java +++ b/core/src/main/java/hudson/model/Executor.java @@ -409,7 +409,15 @@ public SubTask call() throws Exception { if (executable instanceof Actionable) { if (LOGGER.isLoggable(Level.FINER)) { - LOGGER.log(FINER, "when running {0} from {1} we are copying {2} actions whereas the item currently has {3}", new Object[] {executable, workUnit.context.item, workUnit.context.actions, workUnit.context.item.getAllActions()}); + LOGGER.log( + FINER, + "when running {0} from {1} we are copying {2} actions whereas the item currently has {3}", + new Object[] { + executable, + workUnit.context.item, + workUnit.context.actions, + workUnit.context.item.getAllActions(), + }); } for (Action action: workUnit.context.actions) { ((Actionable) executable).addAction(action); @@ -905,7 +913,7 @@ public boolean hasStopPermission() { lock.readLock().lock(); try { return executable != null && getParentOf(executable).getOwnerTask().hasAbortPermission(); - } catch(Exception ex) { + } catch(RuntimeException ex) { if (!(ex instanceof AccessDeniedException)) { // Prevents UI from exploding in the case of unexpected runtime exceptions LOGGER.log(WARNING, "Unhandled exception", ex); diff --git a/core/src/main/java/hudson/model/FileParameterValue.java b/core/src/main/java/hudson/model/FileParameterValue.java index 3e0ff4cda0ee..b3e5c146cd9d 100644 --- a/core/src/main/java/hudson/model/FileParameterValue.java +++ b/core/src/main/java/hudson/model/FileParameterValue.java @@ -27,15 +27,16 @@ import hudson.EnvVars; import hudson.FilePath; import hudson.Launcher; +import hudson.Util; import hudson.tasks.BuildWrapper; import hudson.util.VariableResolver; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.io.UncheckedIOException; import java.io.UnsupportedEncodingException; import java.nio.file.Files; -import java.nio.file.InvalidPathException; import java.util.regex.Pattern; import jenkins.util.SystemProperties; import org.apache.commons.fileupload.FileItem; @@ -72,7 +73,7 @@ public class FileParameterValue extends ParameterValue { * It's not recommended to enable for security reasons. That option is only present for backward compatibility. */ @Restricted(NoExternalUse.class) - @SuppressFBWarnings("MS_SHOULD_BE_FINAL") + @SuppressFBWarnings(value = "MS_SHOULD_BE_FINAL", justification = "for script console") public static /* Script Console modifiable */ boolean ALLOW_FOLDER_TRAVERSAL_OUTSIDE_WORKSPACE = SystemProperties.getBoolean(FileParameterValue.class.getName() + ".allowFolderTraversalOutsideWorkspace"); @@ -152,6 +153,7 @@ public FileItem getFile() { @Override public BuildWrapper createBuildWrapper(AbstractBuild build) { return new BuildWrapper() { + @SuppressFBWarnings(value = {"FILE_UPLOAD_FILENAME", "NP_NULL_ON_SOME_PATH_FROM_RETURN_VALUE"}, justification = "TODO needs triage") @Override public Environment setUp(AbstractBuild build, Launcher launcher, BuildListener listener) throws IOException, InterruptedException { if (!StringUtils.isEmpty(location) && !StringUtils.isEmpty(file.getName())) { @@ -259,11 +261,7 @@ public FileItemImpl(File file) { @Override public InputStream getInputStream() throws IOException { - try { - return Files.newInputStream(file.toPath()); - } catch (InvalidPathException e) { - throw new IOException(e); - } + return Files.newInputStream(Util.fileToPath(file)); } @Override @@ -292,8 +290,8 @@ public byte[] get() { try (InputStream inputStream = Files.newInputStream(file.toPath())) { return IOUtils.toByteArray(inputStream); } - } catch (IOException | InvalidPathException e) { - throw new Error(e); + } catch (IOException e) { + throw new UncheckedIOException(e); } } @@ -314,7 +312,11 @@ public void write(File to) throws Exception { @Override public void delete() { - file.delete(); + try { + Files.deleteIfExists(file.toPath()); + } catch (IOException e) { + throw new UncheckedIOException(e); + } } @Override @@ -338,11 +340,7 @@ public void setFormField(boolean state) { @Override @Deprecated public OutputStream getOutputStream() throws IOException { - try { - return Files.newOutputStream(file.toPath()); - } catch (InvalidPathException e) { - throw new IOException(e); - } + return Files.newOutputStream(Util.fileToPath(file)); } @Override diff --git a/core/src/main/java/hudson/model/Fingerprint.java b/core/src/main/java/hudson/model/Fingerprint.java index 2020c4841b1f..3d49b1977b1f 100644 --- a/core/src/main/java/hudson/model/Fingerprint.java +++ b/core/src/main/java/hudson/model/Fingerprint.java @@ -1416,7 +1416,19 @@ private static void initFacets(@CheckForNull Fingerprint fingerprint) { } @Override public String toString() { - return "Fingerprint[original=" + original + ",hash=" + getHashString() + ",fileName=" + fileName + ",timestamp=" + DATE_CONVERTER.toString(timestamp) + ",usages=" + (usages == null ? "null" : new TreeMap<>(getUsages())) + ",facets=" + facets + "]"; + return "Fingerprint[original=" + + original + + ",hash=" + + getHashString() + + ",fileName=" + + fileName + + ",timestamp=" + + DATE_CONVERTER.toString(timestamp) + + ",usages=" + + (usages == null ? "null" : new TreeMap<>(getUsages())) + + ",facets=" + + facets + + "]"; } /** diff --git a/core/src/main/java/hudson/model/Item.java b/core/src/main/java/hudson/model/Item.java index 05cab8767619..445c83998482 100644 --- a/core/src/main/java/hudson/model/Item.java +++ b/core/src/main/java/hudson/model/Item.java @@ -249,20 +249,82 @@ default void onCreatedFromScratch() { void delete() throws IOException, InterruptedException; PermissionGroup PERMISSIONS = new PermissionGroup(Item.class,Messages._Item_Permissions_Title()); - Permission CREATE = new Permission(PERMISSIONS, "Create", Messages._Item_CREATE_description(), Permission.CREATE, PermissionScope.ITEM_GROUP); - Permission DELETE = new Permission(PERMISSIONS, "Delete", Messages._Item_DELETE_description(), Permission.DELETE, PermissionScope.ITEM); - Permission CONFIGURE = new Permission(PERMISSIONS, "Configure", Messages._Item_CONFIGURE_description(), Permission.CONFIGURE, PermissionScope.ITEM); - Permission READ = new Permission(PERMISSIONS, "Read", Messages._Item_READ_description(), Permission.READ, PermissionScope.ITEM); - Permission DISCOVER = new Permission(PERMISSIONS, "Discover", Messages._AbstractProject_DiscoverPermission_Description(), READ, PermissionScope.ITEM); + Permission CREATE = + new Permission( + PERMISSIONS, + "Create", + Messages._Item_CREATE_description(), + Permission.CREATE, + PermissionScope.ITEM_GROUP); + Permission DELETE = + new Permission( + PERMISSIONS, + "Delete", + Messages._Item_DELETE_description(), + Permission.DELETE, + PermissionScope.ITEM); + Permission CONFIGURE = + new Permission( + PERMISSIONS, + "Configure", + Messages._Item_CONFIGURE_description(), + Permission.CONFIGURE, + PermissionScope.ITEM); + Permission READ = + new Permission( + PERMISSIONS, + "Read", + Messages._Item_READ_description(), + Permission.READ, + PermissionScope.ITEM); + Permission DISCOVER = + new Permission( + PERMISSIONS, + "Discover", + Messages._AbstractProject_DiscoverPermission_Description(), + READ, + PermissionScope.ITEM); /** * Ability to view configuration details. * If the user lacks {@link #CONFIGURE} then any {@link Secret}s must be masked out, even in encrypted form. * @see Secret#ENCRYPTED_VALUE_PATTERN */ - Permission EXTENDED_READ = new Permission(PERMISSIONS,"ExtendedRead", Messages._AbstractProject_ExtendedReadPermission_Description(), CONFIGURE, SystemProperties.getBoolean("hudson.security.ExtendedReadPermission"), new PermissionScope[]{PermissionScope.ITEM}); + Permission EXTENDED_READ = + new Permission( + PERMISSIONS, + "ExtendedRead", + Messages._AbstractProject_ExtendedReadPermission_Description(), + CONFIGURE, + SystemProperties.getBoolean("hudson.security.ExtendedReadPermission"), + new PermissionScope[] {PermissionScope.ITEM}); // TODO the following really belong in Job, not Item, but too late to move since the owner.name is encoded in the ID: - Permission BUILD = new Permission(PERMISSIONS, "Build", Messages._AbstractProject_BuildPermission_Description(), Permission.UPDATE, PermissionScope.ITEM); - Permission WORKSPACE = new Permission(PERMISSIONS, "Workspace", Messages._AbstractProject_WorkspacePermission_Description(), Permission.READ, PermissionScope.ITEM); - Permission WIPEOUT = new Permission(PERMISSIONS, "WipeOut", Messages._AbstractProject_WipeOutPermission_Description(), null, Functions.isWipeOutPermissionEnabled(), new PermissionScope[]{PermissionScope.ITEM}); - Permission CANCEL = new Permission(PERMISSIONS, "Cancel", Messages._AbstractProject_CancelPermission_Description(), Permission.UPDATE, PermissionScope.ITEM); + Permission BUILD = + new Permission( + PERMISSIONS, + "Build", + Messages._AbstractProject_BuildPermission_Description(), + Permission.UPDATE, + PermissionScope.ITEM); + Permission WORKSPACE = + new Permission( + PERMISSIONS, + "Workspace", + Messages._AbstractProject_WorkspacePermission_Description(), + Permission.READ, + PermissionScope.ITEM); + Permission WIPEOUT = + new Permission( + PERMISSIONS, + "WipeOut", + Messages._AbstractProject_WipeOutPermission_Description(), + null, + Functions.isWipeOutPermissionEnabled(), + new PermissionScope[] {PermissionScope.ITEM}); + Permission CANCEL = + new Permission( + PERMISSIONS, + "Cancel", + Messages._AbstractProject_CancelPermission_Description(), + Permission.UPDATE, + PermissionScope.ITEM); } diff --git a/core/src/main/java/hudson/model/ItemGroup.java b/core/src/main/java/hudson/model/ItemGroup.java index 72eea584384b..61989b1711c7 100644 --- a/core/src/main/java/hudson/model/ItemGroup.java +++ b/core/src/main/java/hudson/model/ItemGroup.java @@ -99,7 +99,8 @@ default Stream getItemsStream(Predicate pred) { /** * Gets the {@link Item} inside this group that has a given name, or null if it does not exist. - * @return an item whose {@link Item#getName} is {@code name} and whose {@link Item#getParent} is {@code this}, or null if there is no such item, or there is but the current user lacks both {@link Item#DISCOVER} and {@link Item#READ} on it + * @return an item whose {@link Item#getName} is {@code name} and whose {@link Item#getParent} is {@code this}, + * or null if there is no such item, or there is but the current user lacks both {@link Item#DISCOVER} and {@link Item#READ} on it * @throws AccessDeniedException if the current user has {@link Item#DISCOVER} but not {@link Item#READ} on this item */ @CheckForNull T getItem(String name) throws AccessDeniedException; diff --git a/core/src/main/java/hudson/model/ItemGroupMixIn.java b/core/src/main/java/hudson/model/ItemGroupMixIn.java index 7c973d29c709..b1d52d9bfeee 100644 --- a/core/src/main/java/hudson/model/ItemGroupMixIn.java +++ b/core/src/main/java/hudson/model/ItemGroupMixIn.java @@ -33,6 +33,7 @@ import java.io.File; import java.io.IOException; import java.io.InputStream; +import java.io.UncheckedIOException; import java.nio.file.Files; import java.nio.file.StandardCopyOption; import java.util.Map; @@ -97,7 +98,11 @@ protected ItemGroupMixIn(ItemGroup parent, AccessControlled acl) { * Directory that contains sub-directories for each child item. */ public static Map loadChildren(ItemGroup parent, File modulesDir, Function1 key) { - modulesDir.mkdirs(); // make sure it exists + try { + Files.createDirectories(modulesDir.toPath()); + } catch (IOException e) { + throw new UncheckedIOException(e); + } File[] subdirs = modulesDir.listFiles(File::isDirectory); CopyOnWriteMap.Tree configurations = new CopyOnWriteMap.Tree<>(); @@ -219,7 +224,13 @@ public synchronized T copy(T src, String name) throws I while (matcher.find()) { if (Secret.decrypt(matcher.group(1)) != null) { // AccessDeniedException2 does not permit a custom message, and anyway redirecting the user to the login screen is obviously pointless. - throw new AccessDeniedException(Messages.ItemGroupMixIn_may_not_copy_as_it_contains_secrets_and_(src.getFullName(), Jenkins.getAuthentication2().getName(), Item.PERMISSIONS.title, Item.EXTENDED_READ.name, Item.CONFIGURE.name)); + throw new AccessDeniedException( + Messages.ItemGroupMixIn_may_not_copy_as_it_contains_secrets_and_( + src.getFullName(), + Jenkins.getAuthentication2().getName(), + Item.PERMISSIONS.title, + Item.EXTENDED_READ.name, + Item.CONFIGURE.name)); } } } @@ -260,9 +271,9 @@ public synchronized TopLevelItem createProjectFromXML(String name, InputStream x // place it as config.xml File configXml = Items.getConfigFile(getRootDirFor(name)).getFile(); final File dir = configXml.getParentFile(); - dir.mkdirs(); boolean success = false; try { + Files.createDirectories(dir.toPath()); XMLUtils.safeTransform(new StreamSource(xml), new StreamResult(configXml)); // load it diff --git a/core/src/main/java/hudson/model/Job.java b/core/src/main/java/hudson/model/Job.java index 208298c218c7..dc1ac34a4282 100644 --- a/core/src/main/java/hudson/model/Job.java +++ b/core/src/main/java/hudson/model/Job.java @@ -29,6 +29,7 @@ import com.infradna.tool.bridge_method_injector.WithBridgeMethods; import edu.umd.cs.findbugs.annotations.CheckForNull; import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import hudson.BulkChange; import hudson.EnvVars; import hudson.Extension; @@ -71,6 +72,7 @@ import java.awt.Paint; import java.io.File; import java.io.IOException; +import java.nio.file.Files; import java.util.ArrayList; import java.util.Calendar; import java.util.Collection; @@ -130,6 +132,7 @@ * * @author Kohsuke Kawaguchi */ +@SuppressFBWarnings(value = "SE_BAD_FIELD", justification = "TODO needs triage") public abstract class Job, RunT extends Run> extends AbstractItem implements ExtensionPoint, StaplerOverridable, ModelObjectWithChildren { @@ -660,13 +663,9 @@ public void renameTo(String newName) throws IOException { File oldBuildDir = getBuildDir(); super.renameTo(newName); File newBuildDir = getBuildDir(); - if (oldBuildDir.isDirectory() && !newBuildDir.isDirectory()) { - if (!newBuildDir.getParentFile().isDirectory()) { - newBuildDir.getParentFile().mkdirs(); - } - if (!oldBuildDir.renameTo(newBuildDir)) { - throw new IOException("failed to rename " + oldBuildDir + " to " + newBuildDir); - } + if (Files.isDirectory(Util.fileToPath(oldBuildDir)) && !Files.isDirectory(Util.fileToPath(newBuildDir))) { + Files.createDirectories(Util.fileToPath(newBuildDir.getParentFile())); + Files.move(Util.fileToPath(oldBuildDir), Util.fileToPath(newBuildDir)); } } diff --git a/core/src/main/java/hudson/model/ListView.java b/core/src/main/java/hudson/model/ListView.java index 02946a4e3cea..341a59b0e47f 100644 --- a/core/src/main/java/hudson/model/ListView.java +++ b/core/src/main/java/hudson/model/ListView.java @@ -185,7 +185,7 @@ public DescribableList> getColumns() return columns; } - public Set getJobNames() { + public synchronized Set getJobNames() { return Collections.unmodifiableSet(jobNames); } diff --git a/core/src/main/java/hudson/model/LoadStatistics.java b/core/src/main/java/hudson/model/LoadStatistics.java index 1b42c1626859..a1d84a6b353b 100644 --- a/core/src/main/java/hudson/model/LoadStatistics.java +++ b/core/src/main/java/hudson/model/LoadStatistics.java @@ -378,7 +378,7 @@ protected LoadStatisticsSnapshot computeSnapshot(Iterable q /** * Load statistics clock cycle in milliseconds. Specify a small value for quickly debugging this feature and node provisioning through cloud. */ - @SuppressFBWarnings("MS_SHOULD_BE_FINAL") + @SuppressFBWarnings(value = "MS_SHOULD_BE_FINAL", justification = "for script console") public static int CLOCK = SystemProperties.getInteger(LoadStatistics.class.getName() + ".clock", (int)TimeUnit.SECONDS.toMillis(10)); /** diff --git a/core/src/main/java/hudson/model/ManagementLink.java b/core/src/main/java/hudson/model/ManagementLink.java index 32293017c710..86a8cd6195ec 100644 --- a/core/src/main/java/hudson/model/ManagementLink.java +++ b/core/src/main/java/hudson/model/ManagementLink.java @@ -188,7 +188,9 @@ public enum Category { */ TROUBLESHOOTING(Messages._ManagementLink_Category_TROUBLESHOOTING()), /** - * Tools are specifically tools for administrators, such as the Jenkins CLI and Script Console, as well as specific stand-alone administrative features ({@link jenkins.management.ShutdownLink}, {@link jenkins.management.ReloadLink}). + * Tools are specifically tools for administrators, + * such as the Jenkins CLI and Script Console, + * as well as specific stand-alone administrative features ({@link jenkins.management.ShutdownLink}, {@link jenkins.management.ReloadLink}). * This has nothing to do with build tools or tool installers. */ TOOLS(Messages._ManagementLink_Category_TOOLS()), diff --git a/core/src/main/java/hudson/model/Node.java b/core/src/main/java/hudson/model/Node.java index 11d1f3b3e1c7..03c6d5402632 100644 --- a/core/src/main/java/hudson/model/Node.java +++ b/core/src/main/java/hudson/model/Node.java @@ -99,7 +99,7 @@ public abstract class Node extends AbstractModelObject implements Reconfigurable private static final Logger LOGGER = Logger.getLogger(Node.class.getName()); /** @see JENKINS-46652 */ - @SuppressFBWarnings("MS_SHOULD_BE_FINAL") + @SuppressFBWarnings(value = "MS_SHOULD_BE_FINAL", justification = "for script console") public static /* not final */ boolean SKIP_BUILD_CHECK_ON_FLYWEIGHTS = SystemProperties.getBoolean(Node.class.getName() + ".SKIP_BUILD_CHECK_ON_FLYWEIGHTS", true); /** diff --git a/core/src/main/java/hudson/model/PermalinkProjectAction.java b/core/src/main/java/hudson/model/PermalinkProjectAction.java index bb99b26e8cc6..301c96677710 100644 --- a/core/src/main/java/hudson/model/PermalinkProjectAction.java +++ b/core/src/main/java/hudson/model/PermalinkProjectAction.java @@ -24,6 +24,7 @@ package hudson.model; import edu.umd.cs.findbugs.annotations.CheckForNull; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; import jenkins.model.PeepholePermalink; @@ -136,6 +137,7 @@ public String getId() { return "lastSuccessfulBuild"; } + @SuppressFBWarnings(value = "NP_NULL_ON_SOME_PATH_FROM_RETURN_VALUE", justification = "TODO needs triage") @Override public boolean apply(Run run) { return !run.isBuilding() && run.getResult().isBetterOrEqualTo(Result.UNSTABLE); diff --git a/core/src/main/java/hudson/model/Project.java b/core/src/main/java/hudson/model/Project.java index 26232f942375..3ed33d9bcd1a 100644 --- a/core/src/main/java/hudson/model/Project.java +++ b/core/src/main/java/hudson/model/Project.java @@ -240,28 +240,28 @@ protected List createTransientActions() { for (BuildStep step : getBuildersList()) { try { r.addAll(step.getProjectActions(this)); - } catch (Exception e) { + } catch (RuntimeException e) { LOGGER.log(Level.SEVERE, "Error loading build step.", e); } } for (BuildStep step : getPublishersList()) { try { r.addAll(step.getProjectActions(this)); - } catch (Exception e) { + } catch (RuntimeException e) { LOGGER.log(Level.SEVERE, "Error loading publisher.", e); } } for (BuildWrapper step : getBuildWrappers().values()) { try { r.addAll(step.getProjectActions(this)); - } catch (Exception e) { + } catch (RuntimeException e) { LOGGER.log(Level.SEVERE, "Error loading build wrapper.", e); } } for (Trigger trigger : triggers()) { try { r.addAll(trigger.getProjectActions()); - } catch (Exception e) { + } catch (RuntimeException e) { LOGGER.log(Level.SEVERE, "Error loading trigger.", e); } } diff --git a/core/src/main/java/hudson/model/Queue.java b/core/src/main/java/hudson/model/Queue.java index 64af981be6fc..9d3bfb1194b4 100644 --- a/core/src/main/java/hudson/model/Queue.java +++ b/core/src/main/java/hudson/model/Queue.java @@ -78,6 +78,9 @@ import java.io.IOException; import java.lang.ref.WeakReference; import java.nio.channels.ClosedByInterruptException; +import java.nio.file.Files; +import java.nio.file.InvalidPathException; +import java.nio.file.StandardCopyOption; import java.util.ArrayList; import java.util.Arrays; import java.util.Calendar; @@ -112,6 +115,7 @@ import jenkins.security.QueueItemAuthenticatorProvider; import jenkins.security.stapler.StaplerAccessibleType; import jenkins.util.AtmostOneTaskExecutor; +import jenkins.util.Listeners; import jenkins.util.SystemProperties; import jenkins.util.Timer; import net.jcip.annotations.GuardedBy; @@ -380,7 +384,7 @@ public void load() { pendings.clear(); File queueFile = getXMLQueueFile(); - if (queueFile.exists()) { + if (Files.exists(queueFile.toPath())) { Object unmarshaledObj = new XmlFile(XSTREAM, queueFile).read(); List items; @@ -429,11 +433,9 @@ public void load() { // I don't know how this problem happened, but to diagnose this problem better // when it happens again, save the old queue file for introspection. File bk = new File(queueFile.getPath() + ".bak"); - bk.delete(); - queueFile.renameTo(bk); - queueFile.delete(); + Files.move(queueFile.toPath(), bk.toPath(), StandardCopyOption.REPLACE_EXISTING); } - } catch (IOException e) { + } catch (IOException | InvalidPathException e) { LOGGER.log(Level.WARNING, "Failed to load the queue file " + getXMLQueueFile(), e); } finally { updateSnapshot(); } } finally { lock.unlock(); @@ -2453,7 +2455,7 @@ public String getName() { @Restricted(NoExternalUse.class) @ExportedBean(defaultVisibility = 999) @SuppressFBWarnings(value = "URF_UNREAD_PUBLIC_OR_PROTECTED_FIELD", justification = "it is exported, so it might be used") - public class StubItem { + public static class StubItem { @Exported public StubTask task; @@ -2550,14 +2552,7 @@ public CauseOfBlockage getCauseOfBlockage() { @Override /*package*/ void enter(Queue q) { if (q.waitingList.add(this)) { - for (QueueListener ql : QueueListener.all()) { - try { - ql.onEnterWaiting(this); - } catch (Throwable e) { - // don't let this kill the queue - LOGGER.log(Level.WARNING, "QueueListener failed while processing "+this,e); - } - } + Listeners.notify(QueueListener.class, true, l -> l.onEnterWaiting(this)); } } @@ -2565,14 +2560,7 @@ public CauseOfBlockage getCauseOfBlockage() { /*package*/ boolean leave(Queue q) { boolean r = q.waitingList.remove(this); if (r) { - for (QueueListener ql : QueueListener.all()) { - try { - ql.onLeaveWaiting(this); - } catch (Throwable e) { - // don't let this kill the queue - LOGGER.log(Level.WARNING, "QueueListener failed while processing "+this,e); - } - } + Listeners.notify(QueueListener.class, true, l -> l.onLeaveWaiting(this)); } return r; } @@ -2643,14 +2631,7 @@ public CauseOfBlockage getCauseOfBlockage() { /*package*/ void enter(Queue q) { LOGGER.log(Level.FINE, "{0} is blocked", this); blockedProjects.add(this); - for (QueueListener ql : QueueListener.all()) { - try { - ql.onEnterBlocked(this); - } catch (Throwable e) { - // don't let this kill the queue - LOGGER.log(Level.WARNING, "QueueListener failed while processing "+this,e); - } - } + Listeners.notify(QueueListener.class, true, l -> l.onEnterBlocked(this)); } @Override @@ -2658,14 +2639,7 @@ public CauseOfBlockage getCauseOfBlockage() { boolean r = blockedProjects.remove(this); if (r) { LOGGER.log(Level.FINE, "{0} no longer blocked", this); - for (QueueListener ql : QueueListener.all()) { - try { - ql.onLeaveBlocked(this); - } catch (Throwable e) { - // don't let this kill the queue - LOGGER.log(Level.WARNING, "QueueListener failed while processing "+this,e); - } - } + Listeners.notify(QueueListener.class, true, l -> l.onLeaveBlocked(this)); } return r; } @@ -2752,14 +2726,7 @@ public boolean isPending() { @Override /*package*/ void enter(Queue q) { q.buildables.add(this); - for (QueueListener ql : QueueListener.all()) { - try { - ql.onEnterBuildable(this); - } catch (Throwable e) { - // don't let this kill the queue - LOGGER.log(Level.WARNING, "QueueListener failed while processing "+this,e); - } - } + Listeners.notify(QueueListener.class, true, l -> l.onEnterBuildable(this)); } @Override @@ -2767,14 +2734,7 @@ public boolean isPending() { boolean r = q.buildables.remove(this); if (r) { LOGGER.log(Level.FINE, "{0} no longer blocked", this); - for (QueueListener ql : QueueListener.all()) { - try { - ql.onLeaveBuildable(this); - } catch (Throwable e) { - // don't let this kill the queue - LOGGER.log(Level.WARNING, "QueueListener failed while processing "+this,e); - } - } + Listeners.notify(QueueListener.class, true, l -> l.onLeaveBuildable(this)); } return r; } @@ -2830,14 +2790,7 @@ public boolean isCancelled() { @Override void enter(Queue q) { q.leftItems.put(getId(),this); - for (QueueListener ql : QueueListener.all()) { - try { - ql.onLeft(this); - } catch (Throwable e) { - // don't let this kill the queue - LOGGER.log(Level.WARNING, "QueueListener failed while processing "+this,e); - } - } + Listeners.notify(QueueListener.class, true, l -> l.onLeft(this)); } @Override diff --git a/core/src/main/java/hudson/model/Run.java b/core/src/main/java/hudson/model/Run.java index 37836a9c4c24..e331a9e0c172 100644 --- a/core/src/main/java/hudson/model/Run.java +++ b/core/src/main/java/hudson/model/Run.java @@ -100,6 +100,7 @@ import java.util.List; import java.util.Locale; import java.util.Map; +import java.util.Objects; import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; @@ -814,6 +815,7 @@ public boolean hasntStartedYet() { return state ==State.NOT_STARTED; } + @SuppressFBWarnings(value = "RCN_REDUNDANT_NULLCHECK_OF_NONNULL_VALUE", justification = "see JENKINS-45892") @Override public String toString() { if (project == null) { @@ -1464,7 +1466,8 @@ public Collection getBuildFingerprints() { /** * Returns the log file. * @return The file may reference both uncompressed or compressed logs - * @deprecated Assumes file-based storage of the log, which is not necessarily the case for Pipelines after JEP-210. Use other methods giving various kinds of streams such as {@link Run#getLogReader()}, {@link Run#getLogInputStream()}, or {@link Run#getLogText()}. + * @deprecated Assumes file-based storage of the log, which is not necessarily the case for Pipelines after JEP-210. + * Use other methods giving various kinds of streams such as {@link Run#getLogReader()}, {@link Run#getLogInputStream()}, or {@link Run#getLogText()}. */ @Deprecated public @NonNull File getLogFile() { @@ -1910,7 +1913,7 @@ protected final void execute(@NonNull RunExecution job) { } // even if the main build fails fatally, try to run post build processing - job.post(listener); + job.post(Objects.requireNonNull(listener)); } catch (ThreadDeath t) { throw t; @@ -2651,7 +2654,7 @@ public Object getTarget() { * Escape hatch for StaplerProxy-based access control */ @Restricted(NoExternalUse.class) - @SuppressFBWarnings("MS_SHOULD_BE_FINAL") + @SuppressFBWarnings(value = "MS_SHOULD_BE_FINAL", justification = "for script console") public static /* Script Console modifiable */ boolean SKIP_PERMISSION_CHECK = SystemProperties.getBoolean(Run.class.getName() + ".skipPermissionCheck"); diff --git a/core/src/main/java/hudson/model/RunMap.java b/core/src/main/java/hudson/model/RunMap.java index 7cad5ff9a057..6b2ce262acc4 100644 --- a/core/src/main/java/hudson/model/RunMap.java +++ b/core/src/main/java/hudson/model/RunMap.java @@ -29,6 +29,8 @@ import java.io.File; import java.io.IOException; +import java.io.UncheckedIOException; +import java.nio.file.Files; import java.util.Collections; import java.util.Comparator; import java.util.Iterator; @@ -186,13 +188,17 @@ protected String getIdOf(R r) { public R put(R r) { // Defense against JENKINS-23152 and its ilk. File rootDir = r.getRootDir(); - if (rootDir.isDirectory()) { + if (Files.isDirectory(rootDir.toPath())) { throw new IllegalStateException("JENKINS-23152: " + rootDir + " already existed; will not overwrite with " + r); } if (!r.getClass().getName().equals("hudson.matrix.MatrixRun")) { // JENKINS-26739: grandfathered in proposeNewNumber(r.getNumber()); } - rootDir.mkdirs(); + try { + Files.createDirectories(rootDir.toPath()); + } catch (IOException e) { + throw new UncheckedIOException(e); + } return super._put(r); } diff --git a/core/src/main/java/hudson/model/UpdateCenter.java b/core/src/main/java/hudson/model/UpdateCenter.java index 4a215ad17f44..fdcaa3ada6f4 100644 --- a/core/src/main/java/hudson/model/UpdateCenter.java +++ b/core/src/main/java/hudson/model/UpdateCenter.java @@ -68,8 +68,10 @@ import java.net.URL; import java.net.URLConnection; import java.net.UnknownHostException; +import java.nio.file.AtomicMoveNotSupportedException; import java.nio.file.Files; import java.nio.file.InvalidPathException; +import java.nio.file.StandardCopyOption; import java.security.DigestOutputStream; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; @@ -443,7 +445,7 @@ public HttpResponse doIncompleteInstallStatus() { jobs = Collections.emptyMap(); } return HttpResponses.okJSON(jobs); - } catch (Exception e) { + } catch (RuntimeException e) { return HttpResponses.errorJSON(String.format("ERROR: %s", e.getMessage())); } } @@ -513,7 +515,7 @@ public HttpResponse doInstallStatus(StaplerRequest request) { } } return HttpResponses.okJSON(JSONObject.fromObject(response)); - } catch (Exception e) { + } catch (RuntimeException e) { return HttpResponses.errorJSON(String.format("ERROR: %s", e.getMessage())); } } @@ -1258,8 +1260,15 @@ public File download(DownloadJob job, URL src) throws IOException { // particularly noticeable during 2.0 install when downloading // many plugins con.setReadTimeout(PLUGIN_DOWNLOAD_READ_TIMEOUT); - - int total = con.getContentLength(); + + long total; + final long sizeFromMetadata = job.getContentLength(); + if (sizeFromMetadata == -1) { + // Update site does not advertise a file size, so fall back to download file size, if any + total = con.getContentLength(); + } else { + total = sizeFromMetadata; + } byte[] buf = new byte[8192]; int len; @@ -1279,7 +1288,11 @@ public File download(DownloadJob job, URL src) throws IOException { CountingInputStream cin = new CountingInputStream(in)) { while ((len = cin.read(buf)) >= 0) { out.write(buf,0,len); - job.status = job.new Installing(total == -1 ? -1 : cin.getCount() * 100 / total); + final int count = cin.getCount(); + job.status = job.new Installing(total == -1 ? -1 : ((int) (count * 100 / total))); + if (total != -1 && total < count) { + throw new IOException("Received more data than expected. Expected " + total + " bytes but got " + count + " bytes (so far), aborting download."); + } } } catch (IOException | InvalidPathException e) { throw new IOException("Failed to load "+src+" to "+tmp,e); @@ -1880,14 +1893,21 @@ protected void _run() throws IOException, InstallationStatus { * Called when the download is completed to overwrite * the old file with the new file. */ - protected void replace(File dst, File src) throws IOException { + protected synchronized void replace(File dst, File src) throws IOException { File bak = Util.changeExtension(dst,".bak"); - bak.delete(); - dst.renameTo(bak); - dst.delete(); // any failure up to here is no big deal - if(!src.renameTo(dst)) { - throw new IOException("Failed to rename "+src+" to "+dst); - } + moveAtomically(dst, bak); + moveAtomically(src, dst); + } + + /** + * Indicate the expected size of the download as provided in update site + * metadata. + * + * @return the expected size, or -1 if unknown. + * @since TODO + */ + public long getContentLength() { + return -1; } /** @@ -2158,6 +2178,12 @@ public String getDisplayName() { return plugin.getDisplayName(); } + @Override + public long getContentLength() { + final Long size = plugin.getFileSize(); + return size == null ? -1 : size; + } + @Override public void _run() throws IOException, InstallationStatus { if (wasInstalled()) { @@ -2257,23 +2283,18 @@ protected void replace(File dst, File src) throws IOException { verifyChecksums(this, plugin, src); } - File bak = Util.changeExtension(dst, ".bak"); - bak.delete(); + synchronized (this) { + File bak = Util.changeExtension(dst, ".bak"); - final File legacy = getLegacyDestination(); - if (legacy.exists()) { - if (!legacy.renameTo(bak)) { - legacy.delete(); + final File legacy = getLegacyDestination(); + if (Files.exists(Util.fileToPath(legacy))) { + moveAtomically(legacy, bak); } - } - if (dst.exists()) { - if (!dst.renameTo(bak)) { - dst.delete(); + if (Files.exists(Util.fileToPath(dst))) { + moveAtomically(dst, bak); } - } - if(!src.renameTo(dst)) { - throw new IOException("Failed to rename "+src+" to "+dst); + moveAtomically(src, dst); } } @@ -2418,11 +2439,8 @@ protected void _run() throws IOException { * current version with backup file */ @Override - protected void replace(File dst, File backup) throws IOException { - dst.delete(); // any failure up to here is no big deal - if(!backup.renameTo(dst)) { - throw new IOException("Failed to rename "+backup+" to "+dst); - } + protected synchronized void replace(File dst, File backup) throws IOException { + moveAtomically(backup, dst); } @Override @@ -2652,7 +2670,7 @@ public Object getTarget() { * Escape hatch for StaplerProxy-based access control */ @Restricted(NoExternalUse.class) - @SuppressFBWarnings("MS_SHOULD_BE_FINAL") + @SuppressFBWarnings(value = "MS_SHOULD_BE_FINAL", justification = "for script console") public static /* Script Console modifiable */ boolean SKIP_PERMISSION_CHECK = SystemProperties.getBoolean(UpdateCenter.class.getName() + ".skipPermissionCheck"); @@ -2674,4 +2692,18 @@ public Object getTarget() { XSTREAM.alias("site",UpdateSite.class); XSTREAM.alias("sites",PersistedList.class); } + + private static void moveAtomically(File src, File target) throws IOException { + try { + Files.move(Util.fileToPath(src), Util.fileToPath(target), StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.ATOMIC_MOVE); + } catch (AtomicMoveNotSupportedException e) { + LOGGER.log(Level.WARNING, "Atomic move not supported. Falling back to non-atomic move.", e); + try { + Files.move(Util.fileToPath(src), Util.fileToPath(target), StandardCopyOption.REPLACE_EXISTING); + } catch (IOException e2) { + e2.addSuppressed(e); + throw e2; + } + } + } } diff --git a/core/src/main/java/hudson/model/UpdateSite.java b/core/src/main/java/hudson/model/UpdateSite.java index 2e17014f1bf8..63c82a96f76a 100644 --- a/core/src/main/java/hudson/model/UpdateSite.java +++ b/core/src/main/java/hudson/model/UpdateSite.java @@ -35,6 +35,7 @@ import edu.umd.cs.findbugs.annotations.CheckForNull; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import hudson.ExtensionList; import hudson.PluginManager; import hudson.PluginWrapper; @@ -372,7 +373,11 @@ public JSONObject getJSONObject() { return o; } catch (JSONException | IOException e) { LOGGER.log(Level.SEVERE,"Failed to parse "+df,e); - df.delete(); // if we keep this file, it will cause repeated failures + try { + df.delete(); // if we keep this file, it will cause repeated failures + } catch (IOException e2) { + LOGGER.log(Level.SEVERE, "Failed to delete " + df, e2); + } return null; } } else { @@ -697,6 +702,10 @@ public static class Entry { @Exported public final String url; + /** + * Size of the file in bytes, or {@code null} if unknown. + */ + private final Long size; // non-private, non-final for test @Restricted(NoExternalUse.class) @@ -723,6 +732,12 @@ public Entry(String sourceId, JSONObject o) { this.sha256 = Util.fixEmptyAndTrim(o.optString("sha256")); this.sha512 = Util.fixEmptyAndTrim(o.optString("sha512")); + Long fileSize = null; + if (o.has("size")) { + fileSize = o.getLong("size"); + } + this.size = fileSize; + String url = o.getString("url"); if (!URI.create(url).isAbsolute()) { if (baseURL == null) { @@ -783,6 +798,17 @@ public Api getApi() { return new Api(this); } + /** + * Size of the file being advertised in bytes, or {@code null} if unspecified/unknown. + * @return size of the file if known, {@code null} otherwise. + * + * @since TODO + */ + // @Exported -- TODO unsure + @Restricted(NoExternalUse.class) + public Long getFileSize() { + return size; + } } /** @@ -1151,7 +1177,7 @@ public Plugin(String sourceId, JSONObject o) { if (releaseTimestamp != null) { try { date = Date.from(Instant.parse(releaseTimestamp)); - } catch (Exception ex) { + } catch (RuntimeException ex) { LOGGER.log(Level.FINE, "Failed to parse releaseTimestamp for " + title + " from " + sourceId, ex); } } @@ -1651,6 +1677,7 @@ public HttpResponse doDowngrade() throws IOException { private static final Logger LOGGER = Logger.getLogger(UpdateSite.class.getName()); // The name uses UpdateCenter for compatibility reason. + @SuppressFBWarnings(value = "MS_SHOULD_BE_FINAL", justification = "for script console") public static boolean neverUpdate = SystemProperties.getBoolean(UpdateCenter.class.getName()+".never"); } diff --git a/core/src/main/java/hudson/model/UsageStatistics.java b/core/src/main/java/hudson/model/UsageStatistics.java index 244f17d1d01c..bc292db26c36 100644 --- a/core/src/main/java/hudson/model/UsageStatistics.java +++ b/core/src/main/java/hudson/model/UsageStatistics.java @@ -299,6 +299,6 @@ private static Cipher toCipher(RSAKey key, int mode) throws GeneralSecurityExcep private static final long DAY = DAYS.toMillis(1); - @SuppressFBWarnings("MS_SHOULD_BE_FINAL") + @SuppressFBWarnings(value = "MS_SHOULD_BE_FINAL", justification = "for script console") public static boolean DISABLED = SystemProperties.getBoolean(UsageStatistics.class.getName()+".disabled"); } diff --git a/core/src/main/java/hudson/model/User.java b/core/src/main/java/hudson/model/User.java index c3012b0db7e7..f88be4dd7ba2 100644 --- a/core/src/main/java/hudson/model/User.java +++ b/core/src/main/java/hudson/model/User.java @@ -129,7 +129,7 @@ public class User extends AbstractModelObject implements AccessControlled, Descr * Escape hatch for StaplerProxy-based access control */ @Restricted(NoExternalUse.class) - @SuppressFBWarnings("MS_SHOULD_BE_FINAL") + @SuppressFBWarnings(value = "MS_SHOULD_BE_FINAL", justification = "for script console") public static /* Script Console modifiable */ boolean SKIP_PERMISSION_CHECK = SystemProperties.getBoolean(User.class.getName() + ".skipPermissionCheck"); /** @@ -141,7 +141,7 @@ public class User extends AbstractModelObject implements AccessControlled, Descr *

* See JENKINS-22346. */ - @SuppressFBWarnings("MS_SHOULD_BE_FINAL") + @SuppressFBWarnings(value = "MS_SHOULD_BE_FINAL", justification = "for script console") public static boolean ALLOW_NON_EXISTENT_USER_TO_LOGIN = SystemProperties.getBoolean(User.class.getName() + ".allowNonExistentUserToLogin"); /** @@ -157,7 +157,7 @@ public class User extends AbstractModelObject implements AccessControlled, Descr * SECURITY-406. */ @Restricted(NoExternalUse.class) - @SuppressFBWarnings("MS_SHOULD_BE_FINAL") + @SuppressFBWarnings(value = "MS_SHOULD_BE_FINAL", justification = "for script console") public static boolean ALLOW_USER_CREATION_VIA_URL = SystemProperties.getBoolean(User.class.getName() + ".allowUserCreationViaUrl"); /** diff --git a/core/src/main/java/hudson/model/View.java b/core/src/main/java/hudson/model/View.java index 9363e881013e..fc94dd9af333 100644 --- a/core/src/main/java/hudson/model/View.java +++ b/core/src/main/java/hudson/model/View.java @@ -29,6 +29,7 @@ import com.thoughtworks.xstream.converters.ConversionException; import com.thoughtworks.xstream.io.StreamException; import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import hudson.DescriptorExtensionList; import hudson.Extension; import hudson.ExtensionPoint; @@ -65,6 +66,7 @@ import java.io.OutputStream; import java.io.Serializable; import java.io.StringWriter; +import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Arrays; @@ -78,6 +80,7 @@ import java.util.LinkedHashSet; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; @@ -104,7 +107,6 @@ import net.sf.json.JSONObject; import org.apache.commons.jelly.JellyContext; import org.apache.commons.lang.StringUtils; -import org.apache.tools.ant.filters.StringInputStream; import org.jenkins.ui.icon.Icon; import org.jenkins.ui.icon.IconSet; import org.kohsuke.accmod.Restricted; @@ -1317,6 +1319,7 @@ public static List allInstantiable() { public static final Permission CONFIGURE = new Permission(PERMISSIONS,"Configure", Messages._View_ConfigurePermission_Description(), Permission.CONFIGURE, PermissionScope.ITEM_GROUP); public static final Permission READ = new Permission(PERMISSIONS,"Read", Messages._View_ReadPermission_Description(), Permission.READ, PermissionScope.ITEM_GROUP); + @SuppressFBWarnings(value = "RV_RETURN_VALUE_IGNORED_NO_SIDE_EFFECT", justification = "to guard against potential future compiler optimizations") @Initializer(before = InitMilestone.SYSTEM_CONFIG_LOADED) @Restricted(DoNotUse.class) public static void registerPermissions() { @@ -1324,7 +1327,9 @@ public static void registerPermissions() { // allowing plugins to adapt the system configuration, which may depend on these permissions // having been registered. Since this method is static and since it follows the above // construction of static permission objects (and therefore their calls to - // PermissionGroup#register), there is nothing further to do in this method. + // PermissionGroup#register), there is nothing further to do in this method. We call + // Objects.hash() to guard against potential future compiler optimizations. + Objects.hash(PERMISSIONS, CREATE, DELETE, CONFIGURE, READ); } // to simplify access from Jelly @@ -1396,7 +1401,7 @@ private static View copy(StaplerRequest req, ViewGroup owner, String name) throw throw new Failure("No such view: "+from); } String xml = Jenkins.XSTREAM.toXML(src); - v = createViewFromXML(name, new StringInputStream(xml)); + v = createViewFromXML(name, new ByteArrayInputStream(xml.getBytes(Charset.defaultCharset()))); return v; } diff --git a/core/src/main/java/hudson/model/ViewJob.java b/core/src/main/java/hudson/model/ViewJob.java index a2b21f51a702..1a69809e154b 100644 --- a/core/src/main/java/hudson/model/ViewJob.java +++ b/core/src/main/java/hudson/model/ViewJob.java @@ -23,6 +23,7 @@ */ package hudson.model; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import hudson.model.Descriptor.FormException; import java.io.IOException; import java.util.LinkedHashSet; @@ -231,5 +232,6 @@ public void run() { * when explicitly requested. * */ + @SuppressFBWarnings(value = "MS_SHOULD_BE_FINAL", justification = "for script console") public static boolean reloadPeriodically = SystemProperties.getBoolean(ViewJob.class.getName()+".reloadPeriodically"); } diff --git a/core/src/main/java/hudson/model/listeners/ItemListener.java b/core/src/main/java/hudson/model/listeners/ItemListener.java index f80456d60b77..13522ef500a6 100644 --- a/core/src/main/java/hudson/model/listeners/ItemListener.java +++ b/core/src/main/java/hudson/model/listeners/ItemListener.java @@ -31,9 +31,9 @@ import hudson.model.ItemGroup; import hudson.model.Items; import hudson.security.ACL; -import java.util.function.Consumer; import java.util.logging.Level; import java.util.logging.Logger; +import jenkins.util.Listeners; /** * Receives notifications about CRUD operations of {@link Item}. @@ -171,23 +171,8 @@ public static ExtensionList all() { return ExtensionList.lookup(ItemListener.class); } - // TODO JENKINS-21224 generalize this to a method perhaps in ExtensionList and use consistently from all listeners - private static void forAll(final Consumer consumer) { - for (ItemListener l : all()) { - try { - consumer.accept(l); - } catch (RuntimeException x) { - LOGGER.log(Level.WARNING, "failed to send event to listener of " + l.getClass(), x); - } - } - } - public static void fireOnCopied(final Item src, final Item result) { - forAll(l -> { - if (l != null) { - l.onCopied(src, result); - } - }); + Listeners.notify(ItemListener.class, false, l -> l.onCopied(src, result)); } /** @@ -212,28 +197,16 @@ public static void checkBeforeCopy(final Item src, final ItemGroup parent) throw } public static void fireOnCreated(final Item item) { - forAll(l -> { - if (l != null) { - l.onCreated(item); - } - }); + Listeners.notify(ItemListener.class, false, l -> l.onCreated(item)); } public static void fireOnUpdated(final Item item) { - forAll(l -> { - if (l != null) { - l.onUpdated(item); - } - }); + Listeners.notify(ItemListener.class, false, l -> l.onUpdated(item)); } /** @since 1.548 */ public static void fireOnDeleted(final Item item) { - forAll(l -> { - if (l != null) { - l.onDeleted(item); - } - }); + Listeners.notify(ItemListener.class, false, l -> l.onDeleted(item)); } /** @@ -254,28 +227,16 @@ public static void fireLocationChange(final Item rootItem, final String oldFullN final String oldName = oldFullName.substring(prefixS); final String newName = rootItem.getName(); assert newName.equals(newFullName.substring(prefixS)); - forAll(l -> { - if (l != null) { - l.onRenamed(rootItem, oldName, newName); - } - }); + Listeners.notify(ItemListener.class, false, l -> l.onRenamed(rootItem, oldName, newName)); } - forAll(l -> { - if (l!= null) { - l.onLocationChanged(rootItem, oldFullName, newFullName); - } - }); + Listeners.notify(ItemListener.class, false, l -> l.onLocationChanged(rootItem, oldFullName, newFullName)); if (rootItem instanceof ItemGroup) { for (final Item child : Items.allItems2(ACL.SYSTEM2, (ItemGroup)rootItem, Item.class)) { final String childNew = child.getFullName(); assert childNew.startsWith(newFullName); assert childNew.charAt(newFullName.length()) == '/'; final String childOld = oldFullName + childNew.substring(newFullName.length()); - forAll(l -> { - if (l != null) { - l.onLocationChanged(child, childOld, childNew); - } - }); + Listeners.notify(ItemListener.class, false, l -> l.onLocationChanged(child, childOld, childNew)); } } } diff --git a/core/src/main/java/hudson/model/listeners/RunListener.java b/core/src/main/java/hudson/model/listeners/RunListener.java index 7d4f5190c822..148f41990839 100644 --- a/core/src/main/java/hudson/model/listeners/RunListener.java +++ b/core/src/main/java/hudson/model/listeners/RunListener.java @@ -45,8 +45,7 @@ import java.io.IOException; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; -import java.util.logging.Level; -import java.util.logging.Logger; +import jenkins.util.Listeners; import org.jvnet.tiger_types.Types; /** @@ -202,28 +201,22 @@ public void unregister() { * Fires the {@link #onCompleted(Run, TaskListener)} event. */ public static void fireCompleted(Run r, @NonNull TaskListener listener) { - for (RunListener l : all()) { - if(l.targetType.isInstance(r)) - try { - l.onCompleted(r,listener); - } catch (Throwable e) { - report(e); - } - } + Listeners.notify(RunListener.class, true, l -> { + if (l.targetType.isInstance(r)) { + l.onCompleted(r, listener); + } + }); } /** * Fires the {@link #onInitialize(Run)} event. */ public static void fireInitialize(Run r) { - for (RunListener l : all()) { - if(l.targetType.isInstance(r)) - try { - l.onInitialize(r); - } catch (Throwable e) { - report(e); - } - } + Listeners.notify(RunListener.class, true, l -> { + if (l.targetType.isInstance(r)) { + l.onInitialize(r); + } + }); } @@ -231,14 +224,11 @@ public static void fireInitialize(Run r) { * Fires the {@link #onStarted(Run, TaskListener)} event. */ public static void fireStarted(Run r, TaskListener listener) { - for (RunListener l : all()) { - if(l.targetType.isInstance(r)) - try { - l.onStarted(r,listener); - } catch (Throwable e) { - report(e); - } - } + Listeners.notify(RunListener.class, true, l -> { + if (l.targetType.isInstance(r)) { + l.onStarted(r, listener); + } + }); } /** @@ -248,28 +238,22 @@ public static void fireFinalized(Run r) { if (!Functions.isExtensionsAvailable()) { return; } - for (RunListener l : all()) { - if(l.targetType.isInstance(r)) - try { - l.onFinalized(r); - } catch (Throwable e) { - report(e); - } - } + Listeners.notify(RunListener.class, true, l -> { + if (l.targetType.isInstance(r)) { + l.onFinalized(r); + } + }); } /** * Fires the {@link #onDeleted} event. */ public static void fireDeleted(Run r) { - for (RunListener l : all()) { - if(l.targetType.isInstance(r)) - try { - l.onDeleted(r); - } catch (Throwable e) { - report(e); - } - } + Listeners.notify(RunListener.class, true, l -> { + if (l.targetType.isInstance(r)) { + l.onDeleted(r); + } + }); } /** @@ -278,11 +262,4 @@ public static void fireDeleted(Run r) { public static ExtensionList all() { return ExtensionList.lookup(RunListener.class); } - - private static void report(Throwable e) { - LOGGER.log(Level.WARNING, "RunListener failed",e); - } - - private static final Logger LOGGER = Logger.getLogger(RunListener.class.getName()); - } diff --git a/core/src/main/java/hudson/model/listeners/SCMPollListener.java b/core/src/main/java/hudson/model/listeners/SCMPollListener.java index d8a348790194..48fa91911790 100644 --- a/core/src/main/java/hudson/model/listeners/SCMPollListener.java +++ b/core/src/main/java/hudson/model/listeners/SCMPollListener.java @@ -29,6 +29,7 @@ import hudson.model.TaskListener; import hudson.scm.PollingResult; import java.io.IOException; +import jenkins.util.Listeners; /** * A hook for listening to polling activities in Jenkins. @@ -67,33 +68,15 @@ public void onPollingSuccess( AbstractProject project, TaskListener listen public void onPollingFailed( AbstractProject project, TaskListener listener, Throwable exception) {} public static void fireBeforePolling( AbstractProject project, TaskListener listener ) { - for (SCMPollListener l : all()) { - try { - l.onBeforePolling(project, listener); - } catch (Exception e) { - /* Make sure, that the listeners do not have any impact on the actual poll */ - } - } + Listeners.notify(SCMPollListener.class, true, l -> l.onBeforePolling(project, listener)); } public static void firePollingSuccess( AbstractProject project, TaskListener listener, PollingResult result ) { - for( SCMPollListener l : all() ) { - try { - l.onPollingSuccess(project, listener, result); - } catch (Exception e) { - /* Make sure, that the listeners do not have any impact on the actual poll */ - } - } + Listeners.notify(SCMPollListener.class, true, l -> l.onPollingSuccess(project, listener, result)); } public static void firePollingFailed( AbstractProject project, TaskListener listener, Throwable exception ) { - for( SCMPollListener l : all() ) { - try { - l.onPollingFailed(project, listener, exception); - } catch (Exception e) { - /* Make sure, that the listeners do not have any impact on the actual poll */ - } - } + Listeners.notify(SCMPollListener.class, true, l -> l.onPollingFailed(project, listener, exception)); } /** diff --git a/core/src/main/java/hudson/model/queue/CauseOfBlockage.java b/core/src/main/java/hudson/model/queue/CauseOfBlockage.java index dc0a59e9a01d..9aad22d4ddc6 100644 --- a/core/src/main/java/hudson/model/queue/CauseOfBlockage.java +++ b/core/src/main/java/hudson/model/queue/CauseOfBlockage.java @@ -9,6 +9,7 @@ import hudson.model.Queue.Task; import hudson.model.TaskListener; import hudson.slaves.Cloud; +import java.util.Objects; import org.jvnet.localizer.Localizable; /** @@ -45,7 +46,7 @@ public void print(TaskListener listener) { * Obtains a simple implementation backed by {@link Localizable}. */ public static CauseOfBlockage fromMessage(@NonNull final Localizable l) { - l.getKey(); // null check + Objects.requireNonNull(l); return new CauseOfBlockage() { @Override public String getShortDescription() { diff --git a/core/src/main/java/hudson/model/queue/WorkUnitContext.java b/core/src/main/java/hudson/model/queue/WorkUnitContext.java index c08d5c6e88ee..1b534855270f 100644 --- a/core/src/main/java/hudson/model/queue/WorkUnitContext.java +++ b/core/src/main/java/hudson/model/queue/WorkUnitContext.java @@ -23,6 +23,7 @@ */ package hudson.model.queue; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import hudson.ExtensionList; import hudson.model.Action; import hudson.model.Executor; @@ -79,6 +80,7 @@ public WorkUnitContext(BuildableItem item) { // +1 for the main task int workUnitSize = task.getSubTasks().size(); startLatch = new Latch(workUnitSize) { + @SuppressFBWarnings(value = "NP_NULL_ON_SOME_PATH_FROM_RETURN_VALUE", justification = "TODO needs triage") @Override protected void onCriteriaMet() { // on behalf of the member Executors, diff --git a/core/src/main/java/hudson/os/SU.java b/core/src/main/java/hudson/os/SU.java index e526caa44d4b..9103c72a866f 100644 --- a/core/src/main/java/hudson/os/SU.java +++ b/core/src/main/java/hudson/os/SU.java @@ -26,6 +26,7 @@ import static hudson.util.jna.GNUCLibrary.LIBC; import com.sun.solaris.EmbeddedSu; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import hudson.FilePath; import hudson.Launcher.LocalLauncher; import hudson.Util; @@ -81,6 +82,7 @@ protected String sudoExe() { return "sudo"; } + @SuppressFBWarnings(value = {"COMMAND_INJECTION", "DM_DEFAULT_ENCODING"}, justification = "TODO needs triage") @Override protected Process sudoWithPass(ArgumentListBuilder args) throws IOException { args.prepend(sudoExe(),"-S"); @@ -105,6 +107,7 @@ protected String sudoExe() { return "/usr/bin/pfexec"; } + @SuppressFBWarnings(value = "COMMAND_INJECTION", justification = "TODO needs triage") @Override protected Process sudoWithPass(ArgumentListBuilder args) throws IOException { listener.getLogger().println("Running with embedded_su"); diff --git a/core/src/main/java/hudson/scm/ChangeLogSet.java b/core/src/main/java/hudson/scm/ChangeLogSet.java index a20e9f493de0..0b0d9c1d0e99 100644 --- a/core/src/main/java/hudson/scm/ChangeLogSet.java +++ b/core/src/main/java/hudson/scm/ChangeLogSet.java @@ -249,7 +249,7 @@ public String getMsgAnnotated() { for (ChangeLogAnnotator a : ChangeLogAnnotator.all()) try { a.annotate(parent.run, this, markup); - } catch(Exception e) { + } catch(RuntimeException e) { LOGGER.info("ChangeLogAnnotator " + a.toString() + " failed to annotate message '" + getMsg() + "'; " + e.getMessage()); } catch(Error e) { LOGGER.severe("ChangeLogAnnotator " + a + " failed to annotate message '" + getMsg() + "'; " + e.getMessage()); diff --git a/core/src/main/java/hudson/scm/SCM.java b/core/src/main/java/hudson/scm/SCM.java index b0753909d080..7b94b07e7dd5 100644 --- a/core/src/main/java/hudson/scm/SCM.java +++ b/core/src/main/java/hudson/scm/SCM.java @@ -388,8 +388,23 @@ public SCMRevisionState _calcRevisionsFromBuild(AbstractBuild build, Launc * this exception should be simply propagated all the way up. * @since 1.568 */ - public PollingResult compareRemoteRevisionWith(@NonNull Job project, @Nullable Launcher launcher, @Nullable FilePath workspace, @NonNull TaskListener listener, @NonNull SCMRevisionState baseline) throws IOException, InterruptedException { - if (project instanceof AbstractProject && Util.isOverridden(SCM.class, getClass(), "compareRemoteRevisionWith", AbstractProject.class, Launcher.class, FilePath.class, TaskListener.class, SCMRevisionState.class)) { + public PollingResult compareRemoteRevisionWith( + @NonNull Job project, + @Nullable Launcher launcher, + @Nullable FilePath workspace, + @NonNull TaskListener listener, + @NonNull SCMRevisionState baseline) + throws IOException, InterruptedException { + if (project instanceof AbstractProject + && Util.isOverridden( + SCM.class, + getClass(), + "compareRemoteRevisionWith", + AbstractProject.class, + Launcher.class, + FilePath.class, + TaskListener.class, + SCMRevisionState.class)) { return compareRemoteRevisionWith((AbstractProject) project, launcher, workspace, listener, baseline); } else { throw new AbstractMethodError("you must override the new overload of compareRemoteRevisionWith"); @@ -478,8 +493,25 @@ private boolean is1_346OrLater() { * @throws AbortException in case of a routine failure * @since 1.568 */ - public void checkout(@NonNull Run build, @NonNull Launcher launcher, @NonNull FilePath workspace, @NonNull TaskListener listener, @CheckForNull File changelogFile, @CheckForNull SCMRevisionState baseline) throws IOException, InterruptedException { - if (build instanceof AbstractBuild && listener instanceof BuildListener && Util.isOverridden(SCM.class, getClass(), "checkout", AbstractBuild.class, Launcher.class, FilePath.class, BuildListener.class, File.class)) { + public void checkout( + @NonNull Run build, + @NonNull Launcher launcher, + @NonNull FilePath workspace, + @NonNull TaskListener listener, + @CheckForNull File changelogFile, + @CheckForNull SCMRevisionState baseline) + throws IOException, InterruptedException { + if (build instanceof AbstractBuild + && listener instanceof BuildListener + && Util.isOverridden( + SCM.class, + getClass(), + "checkout", + AbstractBuild.class, + Launcher.class, + FilePath.class, + BuildListener.class, + File.class)) { if (changelogFile == null) { changelogFile = File.createTempFile("changelog", ".xml"); try { diff --git a/core/src/main/java/hudson/search/Search.java b/core/src/main/java/hudson/search/Search.java index 4da199fa2c5d..372d4bb546c6 100644 --- a/core/src/main/java/hudson/search/Search.java +++ b/core/src/main/java/hudson/search/Search.java @@ -300,6 +300,7 @@ class Tag implements Comparable{ prefixMatch = i.getPath().startsWith(tokenList)?1:0; } + @SuppressFBWarnings(value = "EQ_COMPARETO_USE_OBJECT_EQUALS", justification = "TODO needs triage") @Override public int compareTo(Tag that) { int r = this.prefixMatch -that.prefixMatch; @@ -422,7 +423,7 @@ public Object getTarget() { /** * Escape hatch for StaplerProxy-based access control */ - @SuppressFBWarnings("MS_SHOULD_BE_FINAL") + @SuppressFBWarnings(value = "MS_SHOULD_BE_FINAL", justification = "for script console") @Restricted(NoExternalUse.class) public static /* Script Console modifiable */ boolean SKIP_PERMISSION_CHECK = SystemProperties.getBoolean(Search.class.getName() + ".skipPermissionCheck"); diff --git a/core/src/main/java/hudson/security/AuthenticationProcessingFilter2.java b/core/src/main/java/hudson/security/AuthenticationProcessingFilter2.java index 55f3358adfbd..6764550ab722 100644 --- a/core/src/main/java/hudson/security/AuthenticationProcessingFilter2.java +++ b/core/src/main/java/hudson/security/AuthenticationProcessingFilter2.java @@ -23,6 +23,7 @@ */ package hudson.security; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import hudson.model.User; import java.io.IOException; import java.util.logging.Level; @@ -52,6 +53,7 @@ @Restricted(NoExternalUse.class) public final class AuthenticationProcessingFilter2 extends UsernamePasswordAuthenticationFilter { + @SuppressFBWarnings(value = "HARD_CODE_PASSWORD", justification = "This is a password parameter, not a password") public AuthenticationProcessingFilter2(String authenticationGatewayUrl) { setRequiresAuthenticationRequestMatcher(new AntPathRequestMatcher("/" + authenticationGatewayUrl, "POST")); // Jenkins/login.jelly & SetupWizard/authenticate-security-token.jelly @@ -59,6 +61,7 @@ public AuthenticationProcessingFilter2(String authenticationGatewayUrl) { setPasswordParameter("j_password"); } + @SuppressFBWarnings(value = "RV_RETURN_VALUE_IGNORED_NO_SIDE_EFFECT", justification = "request.getSession(true) does in fact have a side effect") @Override protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException { if (SystemProperties.getInteger(SecurityRealm.class.getName() + ".sessionFixationProtectionMode", 1) == 2) { diff --git a/core/src/main/java/hudson/security/TokenBasedRememberMeServices2.java b/core/src/main/java/hudson/security/TokenBasedRememberMeServices2.java index dc00f93a4395..d9920e625fc4 100644 --- a/core/src/main/java/hudson/security/TokenBasedRememberMeServices2.java +++ b/core/src/main/java/hudson/security/TokenBasedRememberMeServices2.java @@ -66,7 +66,7 @@ public class TokenBasedRememberMeServices2 extends TokenBasedRememberMeServices /** * Escape hatch for the check on the maximum date for the expiration duration of the remember me cookie */ - @SuppressFBWarnings("MS_SHOULD_BE_FINAL") + @SuppressFBWarnings(value = "MS_SHOULD_BE_FINAL", justification = "for script console") public static /* Script Console modifiable */ boolean SKIP_TOO_FAR_EXPIRATION_DATE_CHECK = SystemProperties.getBoolean(TokenBasedRememberMeServices2.class.getName() + ".skipTooFarExpirationDateCheck"); @@ -169,6 +169,7 @@ protected UserDetails processAutoLoginCookie(String[] cookieTokens, HttpServletR } } + @SuppressFBWarnings(value = "NP_NULL_ON_SOME_PATH_FROM_RETURN_VALUE", justification = "TODO needs triage") @Override protected Authentication createSuccessfulAuthentication(HttpServletRequest request, UserDetails userDetails) { Authentication auth = super.createSuccessfulAuthentication(request, userDetails); diff --git a/core/src/main/java/hudson/security/csrf/DefaultCrumbIssuer.java b/core/src/main/java/hudson/security/csrf/DefaultCrumbIssuer.java index fda593033e22..cd1c0f795701 100644 --- a/core/src/main/java/hudson/security/csrf/DefaultCrumbIssuer.java +++ b/core/src/main/java/hudson/security/csrf/DefaultCrumbIssuer.java @@ -39,7 +39,7 @@ public class DefaultCrumbIssuer extends CrumbIssuer { private boolean excludeClientIPFromCrumb; @Restricted(NoExternalUse.class) - @SuppressFBWarnings("MS_SHOULD_BE_FINAL") + @SuppressFBWarnings(value = "MS_SHOULD_BE_FINAL", justification = "for script console") public static /* non-final: Groovy Console */ boolean EXCLUDE_SESSION_ID = SystemProperties.getBoolean(DefaultCrumbIssuer.class.getName() + ".EXCLUDE_SESSION_ID"); @DataBoundConstructor diff --git a/core/src/main/java/hudson/security/csrf/GlobalCrumbIssuerConfiguration.java b/core/src/main/java/hudson/security/csrf/GlobalCrumbIssuerConfiguration.java index b8c3d00fd9a9..70e511cae7e2 100644 --- a/core/src/main/java/hudson/security/csrf/GlobalCrumbIssuerConfiguration.java +++ b/core/src/main/java/hudson/security/csrf/GlobalCrumbIssuerConfiguration.java @@ -76,6 +76,6 @@ public static CrumbIssuer createDefaultCrumbIssuer() { } @Restricted(NoExternalUse.class) - @SuppressFBWarnings("MS_SHOULD_BE_FINAL") + @SuppressFBWarnings(value = "MS_SHOULD_BE_FINAL", justification = "for script console") public static /* non-final */ boolean DISABLE_CSRF_PROTECTION = SystemProperties.getBoolean(GlobalCrumbIssuerConfiguration.class.getName() + ".DISABLE_CSRF_PROTECTION"); } diff --git a/core/src/main/java/hudson/slaves/AbstractCloudSlave.java b/core/src/main/java/hudson/slaves/AbstractCloudSlave.java index 2197d8eb138f..edd7e91e82b6 100644 --- a/core/src/main/java/hudson/slaves/AbstractCloudSlave.java +++ b/core/src/main/java/hudson/slaves/AbstractCloudSlave.java @@ -28,9 +28,8 @@ import hudson.model.Descriptor.FormException; import hudson.model.Slave; import hudson.model.TaskListener; -import hudson.util.StreamTaskListener; +import hudson.util.LogTaskListener; import java.io.IOException; -import java.nio.charset.Charset; import java.util.List; import java.util.logging.Level; import java.util.logging.Logger; @@ -85,8 +84,7 @@ public void terminate() throws InterruptedException, IOException { computer.recordTermination(); } try { - // TODO: send the output to somewhere real - _terminate(new StreamTaskListener(System.out, Charset.defaultCharset())); + _terminate(computer instanceof SlaveComputer ? ((SlaveComputer) computer).getListener() : new LogTaskListener(LOGGER, Level.INFO)); } finally { try { Jenkins.get().removeNode(this); diff --git a/core/src/main/java/hudson/slaves/Cloud.java b/core/src/main/java/hudson/slaves/Cloud.java index 45bbeccdde9a..1c12b1ad5fe8 100644 --- a/core/src/main/java/hudson/slaves/Cloud.java +++ b/core/src/main/java/hudson/slaves/Cloud.java @@ -25,10 +25,13 @@ import edu.umd.cs.findbugs.annotations.CheckForNull; import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import hudson.DescriptorExtensionList; import hudson.Extension; import hudson.ExtensionPoint; import hudson.Util; +import hudson.init.InitMilestone; +import hudson.init.Initializer; import hudson.model.Actionable; import hudson.model.Computer; import hudson.model.Describable; @@ -43,8 +46,11 @@ import hudson.slaves.NodeProvisioner.PlannedNode; import hudson.util.DescriptorList; import java.util.Collection; +import java.util.Objects; import java.util.concurrent.Future; import jenkins.model.Jenkins; +import org.kohsuke.accmod.Restricted; +import org.kohsuke.accmod.restrictions.DoNotUse; import org.kohsuke.stapler.DataBoundConstructor; /** @@ -255,6 +261,19 @@ public static DescriptorExtensionList> all() { Computer.PERMISSIONS, "Provision", Messages._Cloud_ProvisionPermission_Description(), Jenkins.ADMINISTER, PERMISSION_SCOPE ); + @SuppressFBWarnings(value = "RV_RETURN_VALUE_IGNORED_NO_SIDE_EFFECT", justification = "to guard against potential future compiler optimizations") + @Initializer(before = InitMilestone.SYSTEM_CONFIG_LOADED) + @Restricted(DoNotUse.class) + public static void registerPermissions() { + // Pending JENKINS-17200, ensure that the above permissions have been registered prior to + // allowing plugins to adapt the system configuration, which may depend on these permissions + // having been registered. Since this method is static and since it follows the above + // construction of static permission objects (and therefore their calls to + // PermissionGroup#register), there is nothing further to do in this method. We call + // Objects.hash() to guard against potential future compiler optimizations. + Objects.hash(PERMISSION_SCOPE, PROVISION); + } + /** * Parameter object for {@link hudson.slaves.Cloud}. * @since 2.259 diff --git a/core/src/main/java/hudson/slaves/CloudSlaveRetentionStrategy.java b/core/src/main/java/hudson/slaves/CloudSlaveRetentionStrategy.java index 8d619e716315..baa30ace2576 100644 --- a/core/src/main/java/hudson/slaves/CloudSlaveRetentionStrategy.java +++ b/core/src/main/java/hudson/slaves/CloudSlaveRetentionStrategy.java @@ -72,7 +72,7 @@ protected long getIdleMaxTime() { } // for debugging, it's convenient to be able to reduce this time - @SuppressFBWarnings("MS_SHOULD_BE_FINAL") + @SuppressFBWarnings(value = "MS_SHOULD_BE_FINAL", justification = "for script console") public static long TIMEOUT = SystemProperties.getLong(CloudSlaveRetentionStrategy.class.getName()+".timeout", TimeUnit.MINUTES.toMillis(10)); private static final Logger LOGGER = Logger.getLogger(CloudSlaveRetentionStrategy.class.getName()); diff --git a/core/src/main/java/hudson/slaves/ComputerLauncher.java b/core/src/main/java/hudson/slaves/ComputerLauncher.java index 5b23eb6a0055..3394039f4256 100644 --- a/core/src/main/java/hudson/slaves/ComputerLauncher.java +++ b/core/src/main/java/hudson/slaves/ComputerLauncher.java @@ -31,6 +31,7 @@ import hudson.remoting.Channel; import hudson.util.DescriptorList; import hudson.util.StreamTaskListener; +import hudson.util.VersionNumber; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; @@ -38,7 +39,6 @@ import java.io.PrintStream; import java.util.regex.Matcher; import java.util.regex.Pattern; -import org.apache.tools.ant.util.DeweyDecimal; /** * Extension point to allow control over how {@link Computer}s are "launched", @@ -192,7 +192,7 @@ protected static void checkJavaVersion(final PrintStream logger, String javaComm final String versionStr = m.group(1); logger.println(Messages.ComputerLauncher_JavaVersionResult(javaCommand, versionStr)); try { - if (new DeweyDecimal(versionStr).isLessThan(new DeweyDecimal("1.8"))) { + if (new VersionNumber(versionStr).isOlderThan(new VersionNumber("1.8"))) { throw new IOException(Messages .ComputerLauncher_NoJavaFound(line)); } diff --git a/core/src/main/java/hudson/slaves/DumbSlave.java b/core/src/main/java/hudson/slaves/DumbSlave.java index 5f5fef1c10c2..f8eb0dc45604 100644 --- a/core/src/main/java/hudson/slaves/DumbSlave.java +++ b/core/src/main/java/hudson/slaves/DumbSlave.java @@ -55,7 +55,17 @@ public DumbSlave(String name, String nodeDescription, String remoteFS, String nu * Use {@link #DumbSlave(String, String, ComputerLauncher)} and configure the rest through setters. */ @Deprecated - public DumbSlave(String name, String nodeDescription, String remoteFS, String numExecutors, Node.Mode mode, String labelString, ComputerLauncher launcher, RetentionStrategy retentionStrategy, List> nodeProperties) throws IOException, FormException { + public DumbSlave( + String name, + String nodeDescription, + String remoteFS, + String numExecutors, + Node.Mode mode, + String labelString, + ComputerLauncher launcher, + RetentionStrategy retentionStrategy, + List> nodeProperties) + throws IOException, FormException { super(name, nodeDescription, remoteFS, numExecutors, mode, labelString, launcher, retentionStrategy, nodeProperties); } diff --git a/core/src/main/java/hudson/slaves/NodeProvisioner.java b/core/src/main/java/hudson/slaves/NodeProvisioner.java index ce9916d84cd0..ec3e7d35dac1 100644 --- a/core/src/main/java/hudson/slaves/NodeProvisioner.java +++ b/core/src/main/java/hudson/slaves/NodeProvisioner.java @@ -800,9 +800,9 @@ public static class NodeProvisionerInvoker extends PeriodicWork { * Give some initial warm up time so that statically connected agents * can be brought online before we start allocating more. */ - @SuppressFBWarnings("MS_SHOULD_BE_FINAL") + @SuppressFBWarnings(value = "MS_SHOULD_BE_FINAL", justification = "for script console") public static int INITIALDELAY = SystemProperties.getInteger(NodeProvisioner.class.getName()+".initialDelay",LoadStatistics.CLOCK*10); - @SuppressFBWarnings("MS_SHOULD_BE_FINAL") + @SuppressFBWarnings(value = "MS_SHOULD_BE_FINAL", justification = "for script console") public static int RECURRENCEPERIOD = SystemProperties.getInteger(NodeProvisioner.class.getName()+".recurrencePeriod",LoadStatistics.CLOCK); @Override diff --git a/core/src/main/java/hudson/slaves/SlaveComputer.java b/core/src/main/java/hudson/slaves/SlaveComputer.java index 72c564fea656..d0c79fae61a4 100644 --- a/core/src/main/java/hudson/slaves/SlaveComputer.java +++ b/core/src/main/java/hudson/slaves/SlaveComputer.java @@ -85,6 +85,7 @@ import jenkins.slaves.JnlpAgentReceiver; import jenkins.slaves.RemotingVersionInfo; import jenkins.slaves.systemInfo.SlaveSystemInfo; +import jenkins.util.Listeners; import jenkins.util.SystemProperties; import org.jenkinsci.remoting.util.LoggingChannelListener; import org.kohsuke.accmod.Restricted; @@ -215,9 +216,10 @@ public Slave getNode() { } /** - * Return the {@link TaskListener} for this SlaveComputer. Never null + * Offers a way to write to the log file for this agent. * @since 2.9 */ + @NonNull public TaskListener getListener() { return taskListener; } @@ -304,7 +306,7 @@ protected Future _connect(boolean forceReconnect) { e.addSuppressed(threadInfo); Functions.printStackTrace(e, taskListener.error(Messages.ComputerLauncher_abortedLaunch())); throw e; - } catch (Exception e) { + } catch (RuntimeException e) { e.addSuppressed(threadInfo); Functions.printStackTrace(e, taskListener.error(Messages.ComputerLauncher_unexpectedError())); throw e; @@ -895,8 +897,7 @@ private void closeChannel() { } catch (IOException e) { logger.log(Level.SEVERE, "Failed to terminate channel to " + getDisplayName(), e); } - for (ComputerListener cl : ComputerListener.all()) - cl.onOffline(this, offlineCause); + Listeners.notify(ComputerListener.class, true, l -> l.onOffline(this, offlineCause)); } } diff --git a/core/src/main/java/hudson/tasks/BuildTrigger.java b/core/src/main/java/hudson/tasks/BuildTrigger.java index 17cd525ad86e..512c3413c4f4 100644 --- a/core/src/main/java/hudson/tasks/BuildTrigger.java +++ b/core/src/main/java/hudson/tasks/BuildTrigger.java @@ -25,6 +25,7 @@ import edu.umd.cs.findbugs.annotations.CheckForNull; import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import hudson.Extension; import hudson.Launcher; import hudson.Util; @@ -295,6 +296,7 @@ public int compare(Dependency lhs, Dependency rhs) { public void buildDependencyGraph(AbstractProject owner, DependencyGraph graph) { for (AbstractProject p : getChildProjects(owner)) // only care about AbstractProject here graph.addDependency(new Dependency(owner, p) { + @SuppressFBWarnings(value = "NP_NULL_ON_SOME_PATH_FROM_RETURN_VALUE", justification = "TODO needs triage") @Override public boolean shouldTriggerBuild(AbstractBuild build, TaskListener listener, List actions) { diff --git a/core/src/main/java/hudson/tasks/LogRotator.java b/core/src/main/java/hudson/tasks/LogRotator.java index 6ac803770629..89ba0cf3a9c4 100644 --- a/core/src/main/java/hudson/tasks/LogRotator.java +++ b/core/src/main/java/hudson/tasks/LogRotator.java @@ -62,7 +62,7 @@ public class LogRotator extends BuildDiscarder { /** @deprecated Replaced by more generic {@link CompositeIOException}. */ @Deprecated - public class CollatedLogRotatorException extends IOException { + public static class CollatedLogRotatorException extends IOException { private static final long serialVersionUID = 5944233808072651101L; public final Collection collated; diff --git a/core/src/main/java/hudson/triggers/SCMTrigger.java b/core/src/main/java/hudson/triggers/SCMTrigger.java index 7c7a23ce206b..8dc4b18179b9 100644 --- a/core/src/main/java/hudson/triggers/SCMTrigger.java +++ b/core/src/main/java/hudson/triggers/SCMTrigger.java @@ -768,6 +768,6 @@ public int hashCode() { /** * How long is too long for a polling activity to be in the queue? */ - @SuppressFBWarnings("MS_SHOULD_BE_FINAL") + @SuppressFBWarnings(value = "MS_SHOULD_BE_FINAL", justification = "for script console") public static long STARVATION_THRESHOLD = SystemProperties.getLong(SCMTrigger.class.getName()+".starvationThreshold", TimeUnit.HOURS.toMillis(1)); } diff --git a/core/src/main/java/hudson/triggers/SlowTriggerAdminMonitor.java b/core/src/main/java/hudson/triggers/SlowTriggerAdminMonitor.java index b821dda31e68..67f7da93151c 100644 --- a/core/src/main/java/hudson/triggers/SlowTriggerAdminMonitor.java +++ b/core/src/main/java/hudson/triggers/SlowTriggerAdminMonitor.java @@ -28,7 +28,7 @@ public class SlowTriggerAdminMonitor extends AdministrativeMonitor { @NonNull private final Map errors = new ConcurrentHashMap<>(); - @SuppressFBWarnings("MS_SHOULD_BE_FINAL") + @SuppressFBWarnings(value = "MS_SHOULD_BE_FINAL", justification = "for script console") public static /* Script Console modifiable */ int MAX_ENTRIES = SystemProperties.getInteger(SlowTriggerAdminMonitor.class.getName() + ".maxEntries", 10); @NonNull @@ -94,7 +94,7 @@ public HttpResponse doClear() { return HttpResponses.redirectViaContextPath("/manage"); } - public class Value { + public static class Value { private final LocalDateTime time; private Class trigger; diff --git a/core/src/main/java/hudson/triggers/Trigger.java b/core/src/main/java/hudson/triggers/Trigger.java index 5fea10010ab4..e54b436480f3 100644 --- a/core/src/main/java/hudson/triggers/Trigger.java +++ b/core/src/main/java/hudson/triggers/Trigger.java @@ -258,6 +258,7 @@ public static void checkTriggers(final Calendar cal) { // terminated. // FIXME allow to set a global crontab spec previousSynchronousPolling = scmd.getExecutor().submit(new DependencyRunner(new ProjectRunnable() { + @SuppressFBWarnings(value = "NP_NULL_ON_SOME_PATH", justification = "TODO needs triage") @Override public void run(AbstractProject p) { for (Trigger t : (Collection) p.getTriggers().values()) { @@ -313,7 +314,7 @@ public void run(AbstractProject p) { /** * Used to be milliseconds, now is seconds since Jenkins 2.289. */ - @SuppressFBWarnings("MS_SHOULD_BE_FINAL") + @SuppressFBWarnings(value = "MS_SHOULD_BE_FINAL", justification = "for script console") @Restricted(NoExternalUse.class) @RestrictedSince("2.289") public static /* non-final for Groovy */ long CRON_THRESHOLD = SystemProperties.getLong(Trigger.class.getName() + ".CRON_THRESHOLD", 30L); // Default threshold 30s @@ -330,7 +331,6 @@ public void run(AbstractProject p) { * * @deprecated Use {@link jenkins.util.Timer#get()} instead. */ - @SuppressWarnings("MS_SHOULD_BE_FINAL") @Deprecated public static @CheckForNull Timer timer; diff --git a/core/src/main/java/hudson/util/ByteBuffer.java b/core/src/main/java/hudson/util/ByteBuffer.java deleted file mode 100644 index 53969d1fffc3..000000000000 --- a/core/src/main/java/hudson/util/ByteBuffer.java +++ /dev/null @@ -1,132 +0,0 @@ -/* - * The MIT License - * - * Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package hudson.util; - -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; - -/** - * {@link ByteArrayOutputStream} re-implementation. - * - *

- * This version allows one to read while writing is in progress. - * - * @author Kohsuke Kawaguchi - * @deprecated since 2008-05-28. Moved to stapler - */ -@Deprecated -public class ByteBuffer extends OutputStream { - private byte[] buf = new byte[8192]; - /** - * Size of the data. - */ - private int size = 0; - - - @Override - public synchronized void write(byte[] b, int off, int len) throws IOException { - ensureCapacity(len); - System.arraycopy(b,off,buf,size,len); - size+=len; - } - - @Override - public synchronized void write(int b) throws IOException { - ensureCapacity(1); - buf[size++] = (byte)b; - } - - public synchronized long length() { - return size; - } - - private void ensureCapacity(int len) { - if(buf.length-size>len) - return; - - byte[] n = new byte[Math.max(buf.length*2, size+len)]; - System.arraycopy(buf,0,n,0,size); - this.buf = n; - } - - @Override - public synchronized String toString() { - return new String(buf,0,size); - } - - /** - * Writes the contents of this buffer to another OutputStream. - */ - public synchronized void writeTo(OutputStream os) throws IOException { - os.write(buf,0,size); - } - - /** - * Creates an {@link InputStream} that reads from the underlying buffer. - */ - public InputStream newInputStream() { - return new InputStream() { - private int pos = 0; - @Override - public int read() throws IOException { - synchronized(ByteBuffer.this) { - if(pos>=size) return -1; - return buf[pos++]; - } - } - - @Override - public int read(byte[] b, int off, int len) throws IOException { - synchronized(ByteBuffer.this) { - if(size==pos) - return -1; - - int sz = Math.min(len,size-pos); - System.arraycopy(buf,pos,b,off,sz); - pos+=sz; - return sz; - } - } - - - @Override - public int available() throws IOException { - synchronized(ByteBuffer.this) { - return size-pos; - } - } - - @Override - public long skip(long n) throws IOException { - synchronized(ByteBuffer.this) { - int diff = (int) Math.min(n,size-pos); - pos+=diff; - return diff; - } - } - }; - } -} diff --git a/core/src/main/java/hudson/util/CompoundEnumeration.java b/core/src/main/java/hudson/util/CompoundEnumeration.java index 6de29d96e5ae..c1679f7a26c7 100644 --- a/core/src/main/java/hudson/util/CompoundEnumeration.java +++ b/core/src/main/java/hudson/util/CompoundEnumeration.java @@ -1,6 +1,7 @@ package hudson.util; import java.util.Arrays; +import java.util.Collections; import java.util.Enumeration; import java.util.Iterator; import java.util.NoSuchElementException; @@ -21,6 +22,11 @@ public CompoundEnumeration(Enumeration... e) { public CompoundEnumeration(Iterable> e) { this.base = e.iterator(); + if (this.base.hasNext()) { + this.cur = this.base.next(); + } else { + this.cur = Collections.emptyEnumeration(); + } } @Override diff --git a/core/src/main/java/hudson/util/CompressedFile.java b/core/src/main/java/hudson/util/CompressedFile.java index 5904555cac6a..48add2d49f10 100644 --- a/core/src/main/java/hudson/util/CompressedFile.java +++ b/core/src/main/java/hudson/util/CompressedFile.java @@ -25,6 +25,8 @@ import com.jcraft.jzlib.GZIPInputStream; import com.jcraft.jzlib.GZIPOutputStream; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; +import hudson.Util; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; @@ -33,7 +35,6 @@ import java.io.OutputStream; import java.io.Reader; import java.nio.file.Files; -import java.nio.file.InvalidPathException; import java.util.concurrent.ExecutorService; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ThreadPoolExecutor; @@ -77,33 +78,22 @@ public CompressedFile(File file) { * Gets the OutputStream to write to the file. */ public OutputStream write() throws IOException { - if(gz.exists()) - gz.delete(); - try { - return Files.newOutputStream(file.toPath()); - } catch (InvalidPathException e) { - throw new IOException(e); - } + Files.deleteIfExists(Util.fileToPath(gz)); + return Files.newOutputStream(Util.fileToPath(file)); } /** * Reads the contents of a file. */ public InputStream read() throws IOException { - if(file.exists()) - try { - return Files.newInputStream(file.toPath()); - } catch (InvalidPathException e) { - throw new IOException(e); - } + if (Files.exists(Util.fileToPath(file))) { + return Files.newInputStream(Util.fileToPath(file)); + } // check if the compressed file exists - if(gz.exists()) - try { - return new GZIPInputStream(Files.newInputStream(gz.toPath())); - } catch (InvalidPathException e) { - throw new IOException(e); - } + if (Files.exists(Util.fileToPath(gz))) { + return new GZIPInputStream(Files.newInputStream(Util.fileToPath(gz))); + } // no such file throw new FileNotFoundException(file.getName()); @@ -144,6 +134,7 @@ public String loadAsString() throws IOException { */ public void compress() { compressionThread.submit(new Runnable() { + @SuppressFBWarnings(value = "RV_RETURN_VALUE_IGNORED_BAD_PRACTICE", justification = "TODO needs triage") @Override public void run() { try { diff --git a/core/src/main/java/hudson/util/FormValidation.java b/core/src/main/java/hudson/util/FormValidation.java index e4cd886bb273..98ad8b7f37eb 100644 --- a/core/src/main/java/hudson/util/FormValidation.java +++ b/core/src/main/java/hudson/util/FormValidation.java @@ -359,7 +359,7 @@ public void validate(File fexe) { } }); return result[0]; - } catch (Exception e) { + } catch (RuntimeException e) { return FormValidation.error(e, "Unexpected error"); } } diff --git a/core/src/main/java/hudson/util/ProcessTree.java b/core/src/main/java/hudson/util/ProcessTree.java index 1b42f348d579..2a0d3c6e2358 100644 --- a/core/src/main/java/hudson/util/ProcessTree.java +++ b/core/src/main/java/hudson/util/ProcessTree.java @@ -36,6 +36,7 @@ import com.sun.jna.ptr.NativeLongByReference; import edu.umd.cs.findbugs.annotations.CheckForNull; import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import hudson.EnvVars; import hudson.FilePath; import hudson.Util; @@ -1750,6 +1751,7 @@ private void parse() { int argmax = argmaxRef.getValue(); + @SuppressFBWarnings(value = "EQ_DOESNT_OVERRIDE_EQUALS", justification = "Not needed for JNA") class StringArrayMemory extends Memory { private long offset=0; private long length=0; @@ -1777,6 +1779,7 @@ byte peek() { return getByte(offset); } + @SuppressFBWarnings(value = "DM_DEFAULT_ENCODING", justification = "TODO needs triage") String readString() { ByteArrayOutputStream baos = new ByteArrayOutputStream(); byte ch; diff --git a/core/src/main/java/hudson/util/RemotingDiagnostics.java b/core/src/main/java/hudson/util/RemotingDiagnostics.java index 5c435ae0b03a..9bba59bbed66 100644 --- a/core/src/main/java/hudson/util/RemotingDiagnostics.java +++ b/core/src/main/java/hudson/util/RemotingDiagnostics.java @@ -28,6 +28,7 @@ import groovy.lang.GroovyShell; import hudson.FilePath; import hudson.Functions; +import hudson.Util; import hudson.remoting.AsyncFutureImpl; import hudson.remoting.DelegatingCallable; import hudson.remoting.Future; @@ -39,6 +40,7 @@ import java.io.StringWriter; import java.lang.management.ManagementFactory; import java.lang.management.ThreadInfo; +import java.nio.file.Files; import java.util.Collections; import java.util.LinkedHashMap; import java.util.Map; @@ -148,6 +150,7 @@ public String call() throws RuntimeException { } return out.toString(); } + private static final long serialVersionUID = 1L; } /** @@ -160,7 +163,7 @@ private static class GetHeapDump extends MasterToSlaveCallable { + LogRecordRef(LogRecord referent) { + super(referent); + } + } + private int start = 0; - private final LogRecord[] records; - private AtomicInteger size = new AtomicInteger(0); + private final LogRecordRef[] records; + private int size; /** * This constructor is deprecated. It can't access system properties with {@link jenkins.util.SystemProperties} @@ -53,7 +60,7 @@ public RingBufferLogHandler() { } public RingBufferLogHandler(int ringSize) { - records = new LogRecord[ringSize]; + records = new LogRecordRef[ringSize]; } /** @@ -68,17 +75,16 @@ public static int getDefaultRingBufferSize() { @Override public synchronized void publish(LogRecord record) { int len = records.length; - final int tempSize = size.get(); - records[(start+ tempSize)%len]=record; - if(tempSize ==len) { + records[(start + size) % len] = new LogRecordRef(record); + if (size == len) { start = (start+1)%len; } else { - size.incrementAndGet(); + size++; } } public synchronized void clear() { - size.set(0); + size = 0; start = 0; } @@ -89,18 +95,26 @@ public synchronized void clear() { * New records are always placed early in the list. */ public List getView() { + // Since Jenkins.logRecords is a field used as an API, we are forced to implement a dynamic list. return new AbstractList() { @Override public LogRecord get(int index) { // flip the order synchronized (RingBufferLogHandler.this) { - return records[(start+(size.get()-(index+1)))%records.length]; + LogRecord r = records[(start + (size - (index + 1))) % records.length].get(); + // We cannot just omit collected entries due to the List interface. + return r != null ? r : new LogRecord(Level.OFF, ""); } } - @Override public int size() { - return size.get(); + synchronized (RingBufferLogHandler.this) { + // Not actually correct if a log record is added + // after this is called but before the list is iterated. + // However the size should only ever grow, up to the ring buffer max, + // so get(int) should never throw AIOOBE. + return size; + } } }; } diff --git a/core/src/main/java/hudson/util/Secret.java b/core/src/main/java/hudson/util/Secret.java index 6deee7b50820..45385af68e0d 100644 --- a/core/src/main/java/hudson/util/Secret.java +++ b/core/src/main/java/hudson/util/Secret.java @@ -308,7 +308,7 @@ public Object unmarshal(HierarchicalStreamReader reader, final UnmarshallingCont public static final boolean AUTO_ENCRYPT_PASSWORD_CONTROL = SystemProperties.getBoolean(Secret.class.getName() + ".AUTO_ENCRYPT_PASSWORD_CONTROL", true); @Restricted(NoExternalUse.class) - @SuppressFBWarnings("MS_SHOULD_BE_FINAL") + @SuppressFBWarnings(value = "MS_SHOULD_BE_FINAL", justification = "for script console") public static /* non-final */ boolean BLANK_NONSECRET_PASSWORD_FIELDS_WITHOUT_ITEM_CONFIGURE = SystemProperties.getBoolean(Secret.class.getName() + ".BLANK_NONSECRET_PASSWORD_FIELDS_WITHOUT_ITEM_CONFIGURE", true); static { diff --git a/core/src/main/java/hudson/util/StreamResource.java b/core/src/main/java/hudson/util/StreamResource.java index efb55d7232ea..eca55527c195 100644 --- a/core/src/main/java/hudson/util/StreamResource.java +++ b/core/src/main/java/hudson/util/StreamResource.java @@ -23,8 +23,10 @@ */ package hudson.util; +import edu.umd.cs.findbugs.annotations.NonNull; import java.io.IOException; import java.io.InputStream; +import java.util.Objects; import org.apache.tools.ant.types.Resource; /** @@ -38,8 +40,8 @@ public class StreamResource extends Resource { * @param name * Used for display purpose. */ - public StreamResource(String name, InputStream in) { - this.in = in; + public StreamResource(String name, @NonNull InputStream in) { + this.in = Objects.requireNonNull(in); setName(name); } @@ -47,4 +49,24 @@ public StreamResource(String name, InputStream in) { public InputStream getInputStream() throws IOException { return in; } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + if (!super.equals(o)) { + return false; + } + StreamResource resource = (StreamResource) o; + return Objects.equals(in, resource.in); + } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), in); + } } diff --git a/core/src/main/java/hudson/util/TextFile.java b/core/src/main/java/hudson/util/TextFile.java index 6be87b736b19..99be26384349 100644 --- a/core/src/main/java/hudson/util/TextFile.java +++ b/core/src/main/java/hudson/util/TextFile.java @@ -57,8 +57,8 @@ public boolean exists() { return file.exists(); } - public void delete() { - file.delete(); + public void delete() throws IOException { + Files.deleteIfExists(Util.fileToPath(file)); } /** @@ -96,7 +96,7 @@ public Stream lines() throws IOException { * Overwrites the file by the given string. */ public void write(String text) throws IOException { - file.getParentFile().mkdirs(); + Files.createDirectories(Util.fileToPath(file.getParentFile())); try (AtomicFileWriter w = new AtomicFileWriter(file)) { try { w.write(text); diff --git a/core/src/main/java/hudson/util/XStream2.java b/core/src/main/java/hudson/util/XStream2.java index 0802197390b8..db600cfe9081 100644 --- a/core/src/main/java/hudson/util/XStream2.java +++ b/core/src/main/java/hudson/util/XStream2.java @@ -52,6 +52,7 @@ import com.thoughtworks.xstream.security.AnyTypePermission; import edu.umd.cs.findbugs.annotations.CheckForNull; import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import hudson.PluginManager; import hudson.PluginWrapper; import hudson.XmlFile; @@ -469,11 +470,13 @@ public boolean canConvert(Class type) { return findConverter(type)!=null; } + @SuppressFBWarnings(value = "NP_NULL_ON_SOME_PATH_FROM_RETURN_VALUE", justification = "TODO needs triage") @Override public void marshal(Object source, HierarchicalStreamWriter writer, MarshallingContext context) { findConverter(source.getClass()).marshal(source,writer,context); } + @SuppressFBWarnings(value = "NP_NULL_ON_SOME_PATH_FROM_RETURN_VALUE", justification = "TODO needs triage") @Override public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context) { return findConverter(context.getRequiredType()).unmarshal(reader,context); diff --git a/core/src/main/java/hudson/util/io/ReopenableRotatingFileOutputStream.java b/core/src/main/java/hudson/util/io/ReopenableRotatingFileOutputStream.java index 3800f54c16dc..34c9dbdbf76a 100644 --- a/core/src/main/java/hudson/util/io/ReopenableRotatingFileOutputStream.java +++ b/core/src/main/java/hudson/util/io/ReopenableRotatingFileOutputStream.java @@ -23,8 +23,14 @@ */ package hudson.util.io; +import hudson.Util; import java.io.File; import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.InvalidPathException; +import java.nio.file.StandardCopyOption; +import java.util.logging.Level; +import java.util.logging.Logger; /** * {@link ReopenableFileOutputStream} that does log rotation upon rewind. @@ -34,6 +40,8 @@ * @deprecated due to risk for file leak. Prefer {@link RewindableRotatingFileOutputStream} */ @Deprecated public class ReopenableRotatingFileOutputStream extends ReopenableFileOutputStream { + private static final Logger LOGGER = Logger.getLogger(ReopenableRotatingFileOutputStream.class.getName()); + /** * Number of log files to keep. */ @@ -54,10 +62,9 @@ public void rewind() throws IOException { super.rewind(); for (int i=size-1;i>=0;i--) { File fi = getNumberedFileName(i); - if (fi.exists()) { + if (Files.exists(Util.fileToPath(fi))) { File next = getNumberedFileName(i+1); - next.delete(); - fi.renameTo(next); + Files.move(Util.fileToPath(fi), Util.fileToPath(next), StandardCopyOption.REPLACE_EXISTING); } } } @@ -67,7 +74,11 @@ public void rewind() throws IOException { */ public void deleteAll() { for (int i=0; i<=size; i++) { - getNumberedFileName(i).delete(); + try { + Files.deleteIfExists(getNumberedFileName(i).toPath()); + } catch (IOException | InvalidPathException e) { + LOGGER.log(Level.WARNING, null, e); + } } } } diff --git a/core/src/main/java/hudson/util/io/RewindableRotatingFileOutputStream.java b/core/src/main/java/hudson/util/io/RewindableRotatingFileOutputStream.java index aa53ea9f4c46..07f59f5090d3 100644 --- a/core/src/main/java/hudson/util/io/RewindableRotatingFileOutputStream.java +++ b/core/src/main/java/hudson/util/io/RewindableRotatingFileOutputStream.java @@ -23,8 +23,14 @@ */ package hudson.util.io; +import hudson.Util; import java.io.File; import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.InvalidPathException; +import java.nio.file.StandardCopyOption; +import java.util.logging.Level; +import java.util.logging.Logger; /** * {@link ReopenableFileOutputStream} that does log rotation upon rewind. @@ -33,6 +39,8 @@ * @since 2.18 */ public class RewindableRotatingFileOutputStream extends RewindableFileOutputStream { + private static final Logger LOGGER = Logger.getLogger(RewindableRotatingFileOutputStream.class.getName()); + /** * Number of log files to keep. */ @@ -53,10 +61,9 @@ public void rewind() throws IOException { super.rewind(); for (int i=size-1;i>=0;i--) { File fi = getNumberedFileName(i); - if (fi.exists()) { + if (Files.exists(Util.fileToPath(fi))) { File next = getNumberedFileName(i+1); - next.delete(); - fi.renameTo(next); + Files.move(Util.fileToPath(fi), Util.fileToPath(next), StandardCopyOption.REPLACE_EXISTING); } } } @@ -66,7 +73,11 @@ public void rewind() throws IOException { */ public void deleteAll() { for (int i=0; i<=size; i++) { - getNumberedFileName(i).delete(); + try { + Files.deleteIfExists(getNumberedFileName(i).toPath()); + } catch (IOException | InvalidPathException e) { + LOGGER.log(Level.WARNING, null, e); + } } } } diff --git a/core/src/main/java/jenkins/FilePathFilterAggregator.java b/core/src/main/java/jenkins/FilePathFilterAggregator.java index 728700a77532..0702bebb8048 100644 --- a/core/src/main/java/jenkins/FilePathFilterAggregator.java +++ b/core/src/main/java/jenkins/FilePathFilterAggregator.java @@ -30,7 +30,7 @@ private Entry(FilePathFilter filter, double ordinal) { @Override public int compareTo(Entry that) { - double result = Double.compare(this.ordinal, that.ordinal); + int result = Double.compare(this.ordinal, that.ordinal); if (result < 0) return -1; if (result > 0) return 1; diff --git a/core/src/main/java/jenkins/I18n.java b/core/src/main/java/jenkins/I18n.java index 0fb435484a2b..89dfb6e91c63 100644 --- a/core/src/main/java/jenkins/I18n.java +++ b/core/src/main/java/jenkins/I18n.java @@ -107,7 +107,7 @@ public HttpResponse doResourceBundle(StaplerRequest request) { } return HttpResponses.okJSON(ResourceBundleUtil.getBundle(baseName, locale)); - } catch (Exception e) { + } catch (RuntimeException e) { return HttpResponses.errorJSON(e.getMessage()); } } diff --git a/core/src/main/java/jenkins/JenkinsHttpSessionListener.java b/core/src/main/java/jenkins/JenkinsHttpSessionListener.java index 95a61834a48b..3b9e531072ec 100644 --- a/core/src/main/java/jenkins/JenkinsHttpSessionListener.java +++ b/core/src/main/java/jenkins/JenkinsHttpSessionListener.java @@ -49,7 +49,7 @@ public void sessionCreated(HttpSessionEvent httpSessionEvent) { for (HttpSessionListener listener : HttpSessionListener.all()) { try { listener.sessionCreated(httpSessionEvent); - } catch (Exception e) { + } catch (RuntimeException e) { LOGGER.log(Level.SEVERE, "Error calling HttpSessionListener ExtensionPoint sessionCreated().", e); } } @@ -60,7 +60,7 @@ public void sessionDestroyed(HttpSessionEvent httpSessionEvent) { for (HttpSessionListener listener : HttpSessionListener.all()) { try { listener.sessionDestroyed(httpSessionEvent); - } catch (Exception e) { + } catch (RuntimeException e) { LOGGER.log(Level.SEVERE, "Error calling HttpSessionListener ExtensionPoint sessionDestroyed().", e); } } diff --git a/core/src/main/java/jenkins/MetaLocaleDrivenResourceProvider.java b/core/src/main/java/jenkins/MetaLocaleDrivenResourceProvider.java index 26dac5e1720e..7026fd926fb2 100644 --- a/core/src/main/java/jenkins/MetaLocaleDrivenResourceProvider.java +++ b/core/src/main/java/jenkins/MetaLocaleDrivenResourceProvider.java @@ -51,7 +51,7 @@ public URL lookup(@NonNull String s) { if (url != null) { return url; } - } catch (Exception e) { + } catch (RuntimeException e) { LOGGER.warn("Failed to lookup URL for '" + s + "' from '" + provider.toString(), e); } } diff --git a/core/src/main/java/jenkins/SoloFilePathFilter.java b/core/src/main/java/jenkins/SoloFilePathFilter.java index 19a949bfde7a..48420c4cee5b 100644 --- a/core/src/main/java/jenkins/SoloFilePathFilter.java +++ b/core/src/main/java/jenkins/SoloFilePathFilter.java @@ -23,7 +23,7 @@ public final class SoloFilePathFilter extends FilePathFilter { private static final Logger LOGGER = Logger.getLogger(SoloFilePathFilter.class.getName()); - @SuppressFBWarnings("MS_SHOULD_BE_FINAL") + @SuppressFBWarnings(value = "MS_SHOULD_BE_FINAL", justification = "for script console") @Restricted(NoExternalUse.class) public static /* non-final for Groovy */ boolean REDACT_ERRORS = SystemProperties.getBoolean(SoloFilePathFilter.class.getName() + ".redactErrors", true); diff --git a/core/src/main/java/jenkins/cli/StopBuildsCommand.java b/core/src/main/java/jenkins/cli/StopBuildsCommand.java index d010628df321..edb9c2916ba5 100644 --- a/core/src/main/java/jenkins/cli/StopBuildsCommand.java +++ b/core/src/main/java/jenkins/cli/StopBuildsCommand.java @@ -95,7 +95,7 @@ private void stopBuild(final Run build, executor.doStop(); isAnyBuildStopped = true; stdout.printf("Build '%s' stopped for job '%s'%n", buildName, jobName); - } catch (final Exception e) { + } catch (final RuntimeException e) { stdout.printf("Exception occurred while trying to stop build '%s' for job '%s'. ", buildName, jobName); stdout.printf("Exception class: %s, message: %s%n", e.getClass().getSimpleName(), e.getMessage()); } diff --git a/core/src/main/java/jenkins/diagnosis/HsErrPidFile.java b/core/src/main/java/jenkins/diagnosis/HsErrPidFile.java index d97d508d7614..33b25d3a8fb8 100644 --- a/core/src/main/java/jenkins/diagnosis/HsErrPidFile.java +++ b/core/src/main/java/jenkins/diagnosis/HsErrPidFile.java @@ -4,6 +4,7 @@ import hudson.util.HttpResponses; import java.io.File; import java.io.IOException; +import java.nio.file.Files; import java.util.Date; import jenkins.model.Jenkins; import org.kohsuke.stapler.HttpResponse; @@ -51,7 +52,7 @@ public HttpResponse doDownload() throws IOException { @RequirePOST public HttpResponse doDelete() throws IOException { Jenkins.get().checkPermission(Jenkins.ADMINISTER); - file.delete(); + Files.deleteIfExists(Util.fileToPath(file)); owner.files.remove(this); return HttpResponses.redirectTo("../.."); } diff --git a/core/src/main/java/jenkins/fingerprints/FileFingerprintStorage.java b/core/src/main/java/jenkins/fingerprints/FileFingerprintStorage.java index b477a37b8b13..6d30bdceccd1 100644 --- a/core/src/main/java/jenkins/fingerprints/FileFingerprintStorage.java +++ b/core/src/main/java/jenkins/fingerprints/FileFingerprintStorage.java @@ -39,6 +39,10 @@ import java.io.File; import java.io.IOException; import java.io.PrintWriter; +import java.nio.file.DirectoryStream; +import java.nio.file.Files; +import java.nio.file.InvalidPathException; +import java.nio.file.Path; import java.util.Date; import java.util.Map; import java.util.logging.Level; @@ -99,7 +103,7 @@ public FileFingerprintStorage () {} Fingerprint f = (Fingerprint) loaded; return f; } catch (IOException e) { - if(file.exists() && file.length() == 0) { + if (Files.exists(Util.fileToPath(file)) && Files.size(Util.fileToPath(file)) == 0) { // Despite the use of AtomicFile, there are reports indicating that people often see // empty XML file, presumably either due to file system corruption (perhaps by sudden // power loss, etc.) or abnormal program termination. @@ -107,13 +111,13 @@ public FileFingerprintStorage () {} // but if the file size is 0, which is what's reported in JENKINS-2012, then it seems // like recovering it silently by deleting the file is not a bad idea. logger.log(Level.WARNING, "Size zero fingerprint. Disk corruption? {0}", configFile); - file.delete(); + Files.delete(Util.fileToPath(file)); return null; } String parseError = messageOfParseException(e); if (parseError != null) { logger.log(Level.WARNING, "Malformed XML in {0}: {1}", new Object[] {configFile, parseError}); - file.delete(); + Files.deleteIfExists(Util.fileToPath(file)); return null; } logger.log(Level.WARNING, "Failed to load " + configFile, e); @@ -143,7 +147,7 @@ public void save(Fingerprint fp) throws IOException { */ public static void save(Fingerprint fp, File file) throws IOException { if (fp.getPersistedFacets().isEmpty()) { - file.getParentFile().mkdirs(); + Files.createDirectories(Util.fileToPath(file.getParentFile())); // JENKINS-16301: fast path for the common case. AtomicFileWriter afw = new AtomicFileWriter(file); try (PrintWriter w = new PrintWriter(new BufferedWriter(afw))) { @@ -265,7 +269,7 @@ private boolean cleanFingerprint(File fingerprintFile, TaskListener listener) { Fingerprint fp = loadFingerprint(fingerprintFile); if (fp == null || (!fp.isAlive() && fp.getFacetBlockingDeletion() == null) ) { listener.getLogger().println("deleting obsolete " + fingerprintFile); - fingerprintFile.delete(); + Files.deleteIfExists(fingerprintFile.toPath()); return true; } else { if (!fp.isAlive()) { @@ -277,7 +281,7 @@ private boolean cleanFingerprint(File fingerprintFile, TaskListener listener) { fp = getFingerprint(fp); return fp.trim(); } - } catch (IOException e) { + } catch (IOException | InvalidPathException e) { Functions.printStackTrace(e, listener.error("Failed to process " + fingerprintFile)); return false; } @@ -322,10 +326,19 @@ private static String messageOfParseException(Throwable throwable) { * Deletes a directory if it's empty. */ private void deleteIfEmpty(File dir) { - String[] r = dir.list(); - if(r==null) return; // can happen in a rare occasion - if(r.length==0) - dir.delete(); + try { + if (Files.isDirectory(dir.toPath())) { + boolean isEmpty; + try (DirectoryStream directory = Files.newDirectoryStream(dir.toPath())) { + isEmpty = !directory.iterator().hasNext(); + } + if (isEmpty) { + Files.delete(dir.toPath()); + } + } + } catch (IOException | InvalidPathException e) { + logger.log(Level.WARNING, null, e); + } } protected Fingerprint loadFingerprint(File fingerprintFile) throws IOException { diff --git a/core/src/main/java/jenkins/formelementpath/FormElementPathPageDecorator.java b/core/src/main/java/jenkins/formelementpath/FormElementPathPageDecorator.java index b1c3b87d96f1..8a1df7078135 100644 --- a/core/src/main/java/jenkins/formelementpath/FormElementPathPageDecorator.java +++ b/core/src/main/java/jenkins/formelementpath/FormElementPathPageDecorator.java @@ -8,7 +8,7 @@ @Extension public class FormElementPathPageDecorator extends PageDecorator { - @SuppressFBWarnings("MS_SHOULD_BE_FINAL") + @SuppressFBWarnings(value = "MS_SHOULD_BE_FINAL", justification = "for script console") private static /*almost final */ boolean ENABLED = SystemProperties.getBoolean(FormElementPathPageDecorator.class.getName() + ".enabled"); diff --git a/core/src/main/java/jenkins/install/InstallUtil.java b/core/src/main/java/jenkins/install/InstallUtil.java index 49cf460f3d56..bf126df40a27 100644 --- a/core/src/main/java/jenkins/install/InstallUtil.java +++ b/core/src/main/java/jenkins/install/InstallUtil.java @@ -37,7 +37,9 @@ import hudson.util.VersionNumber; import java.io.File; import java.io.IOException; +import java.io.UncheckedIOException; import java.nio.charset.Charset; +import java.nio.file.Files; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; @@ -162,14 +164,8 @@ private static InstallState getDefaultInstallState() { // Allow for skipping if(shouldNotRun) { - try { - InstallState.INITIAL_SETUP_COMPLETED.initializeState(); - return j.getInstallState(); - } catch (RuntimeException e) { - throw e; - } catch (Exception e) { - throw new RuntimeException(e); - } + InstallState.INITIAL_SETUP_COMPLETED.initializeState(); + return j.getInstallState(); } return InstallState.INITIAL_SECURITY_SETUP; @@ -298,7 +294,11 @@ private static String getCurrentExecVersion() { public static synchronized void persistInstallStatus(List installingPlugins) { File installingPluginsFile = getInstallingPluginsFile(); if(installingPlugins == null || installingPlugins.isEmpty()) { - installingPluginsFile.delete(); + try { + Files.deleteIfExists(installingPluginsFile.toPath()); + } catch (IOException e) { + throw new UncheckedIOException(e); + } return; } LOGGER.fine("Writing install state to: " + installingPluginsFile.getAbsolutePath()); diff --git a/core/src/main/java/jenkins/install/SetupWizard.java b/core/src/main/java/jenkins/install/SetupWizard.java index 7ff266c52592..caac5b987333 100644 --- a/core/src/main/java/jenkins/install/SetupWizard.java +++ b/core/src/main/java/jenkins/install/SetupWizard.java @@ -209,7 +209,7 @@ public String getDisplayName() { try { // Make sure plugin metadata is up to date UpdateCenter.updateDefaultSite(); - } catch (Exception e) { + } catch (RuntimeException e) { LOGGER.log(Level.WARNING, e.getMessage(), e); } } @@ -290,7 +290,7 @@ public boolean isUsingSecurityToken() { try { return !Jenkins.get().getInstallState().isSetupComplete() && isUsingSecurityDefaults(); - } catch (Exception e) { + } catch (RuntimeException e) { // ignore } return false; @@ -589,7 +589,7 @@ public JSONArray getPlatformPluginUpdates() { } else { try { initialPluginList = JSONArray.fromObject(initialPluginJson); - } catch (Exception ex) { + } catch (RuntimeException ex) { /* Second attempt: It's not a remote file, but still wrapped */ initialPluginList = JSONObject.fromObject(initialPluginJson).getJSONArray("categories"); } diff --git a/core/src/main/java/jenkins/management/AsynchronousAdministrativeMonitor.java b/core/src/main/java/jenkins/management/AsynchronousAdministrativeMonitor.java index 71d13d5785e0..65ee4cd653d8 100644 --- a/core/src/main/java/jenkins/management/AsynchronousAdministrativeMonitor.java +++ b/core/src/main/java/jenkins/management/AsynchronousAdministrativeMonitor.java @@ -11,7 +11,9 @@ import hudson.util.StreamTaskListener; import java.io.File; import java.io.IOException; +import java.io.UncheckedIOException; import java.nio.charset.Charset; +import java.nio.file.Files; import java.util.logging.Level; import java.util.logging.Logger; import jenkins.model.Jenkins; @@ -57,7 +59,11 @@ public AnnotatedLargeText getLogText() { */ protected File getLogFile() { File base = getBaseDir(); - base.mkdirs(); + try { + Files.createDirectories(base.toPath()); + } catch (IOException e) { + throw new UncheckedIOException(e); + } return new File(base,"log"); } diff --git a/core/src/main/java/jenkins/model/Jenkins.java b/core/src/main/java/jenkins/model/Jenkins.java index c6c680b516c7..aeaf7f1649c3 100644 --- a/core/src/main/java/jenkins/model/Jenkins.java +++ b/core/src/main/java/jenkins/model/Jenkins.java @@ -217,6 +217,8 @@ import java.net.HttpURLConnection; import java.net.URL; import java.nio.charset.Charset; +import java.nio.file.Files; +import java.nio.file.InvalidPathException; import java.security.SecureRandom; import java.util.ArrayList; import java.util.Arrays; @@ -277,6 +279,7 @@ import jenkins.security.stapler.TypedFilter; import jenkins.slaves.WorkspaceLocator; import jenkins.util.JenkinsJVM; +import jenkins.util.Listeners; import jenkins.util.SystemProperties; import jenkins.util.Timer; import jenkins.util.io.FileBoolean; @@ -673,7 +676,6 @@ private static int getSlaveAgentPortInitialValue(int def) { */ private String label=""; - //@SuppressFBWarnings("MS_SHOULD_BE_FINAL") private static /* non-final for Groovy */ String nodeNameAndSelfLabelOverride = SystemProperties.getString(Jenkins.class.getName() + ".nodeNameAndSelfLabelOverride"); /** @@ -889,6 +891,7 @@ protected Jenkins(File root, ServletContext context) throws IOException, Interru * If non-null, use existing plugin manager. create a new one. */ @SuppressFBWarnings({ + "DMI_RANDOM_USED_ONLY_ONCE", // TODO needs triage "ST_WRITE_TO_STATIC_FROM_INSTANCE_METHOD", // Trigger.timer "DM_EXIT" // Exit is wanted here }) @@ -2431,11 +2434,6 @@ public String getUrlChildPrefix() { */ public @Nullable String getRootUrl() throws IllegalStateException { final JenkinsLocationConfiguration config = JenkinsLocationConfiguration.get(); - if (config == null) { - // Try to get standard message if possible - final Jenkins j = Jenkins.get(); - throw new IllegalStateException("Jenkins instance " + j + " has been successfully initialized, but JenkinsLocationConfiguration is undefined."); - } String url = config.getUrl(); if(url!=null) { return Util.ensureEndsWith(url,"/"); @@ -2452,7 +2450,7 @@ public String getUrlChildPrefix() { @CheckForNull public String getConfiguredRootUrl() { JenkinsLocationConfiguration config = JenkinsLocationConfiguration.get(); - return config != null ? config.getUrl() : null; + return config.getUrl(); } /** @@ -3349,8 +3347,8 @@ private void setBuildsAndWorkspacesDir() throws IOException, InvalidBuildsDir { // make sure platform can handle colon try { File tmp = File.createTempFile("Jenkins-doCheckRawBuildsDir", "foo:bar"); - tmp.delete(); - } catch (IOException e) { + Files.delete(tmp.toPath()); + } catch (IOException | InvalidPathException e) { throw (InvalidBuildsDir)new InvalidBuildsDir(newBuildsDirValue + " contains ${ITEM_FULLNAME} but your system does not support it (JENKINS-12251). Use ${ITEM_FULL_NAME} instead").initCause(e); } } @@ -3544,7 +3542,6 @@ private void saveQuietly() { /** * Called to shut down the system. */ - @edu.umd.cs.findbugs.annotations.SuppressFBWarnings("ST_WRITE_TO_STATIC_FROM_INSTANCE_METHOD") public void cleanUp() { if (theInstance != this && theInstance != null) { LOGGER.log(Level.WARNING, "This instance is no longer the singleton, ignoring cleanUp()"); @@ -4363,8 +4360,8 @@ public void doDoFingerprintCheck( StaplerRequest req, StaplerResponse rsp ) thro /** * For debugging. Expose URL to perform GC. */ - @edu.umd.cs.findbugs.annotations.SuppressFBWarnings("DM_GC") @RequirePOST + @SuppressFBWarnings(value = "DM_GC", justification = "for debugging") public void doGc(StaplerResponse rsp) throws IOException { checkPermission(Jenkins.ADMINISTER); System.gc(); @@ -4499,15 +4496,7 @@ public void run() { // give some time for the browser to load the "reloading" page Thread.sleep(TimeUnit.SECONDS.toMillis(5)); LOGGER.info(String.format("Restarting VM as requested by %s", exitUser)); - for (RestartListener listener : RestartListener.all()) { - try { - listener.onRestart(); - } catch (Throwable t) { - LOGGER.log(Level.WARNING, - "RestartListener failed, ignoring and continuing with restart, this indicates a bug in the associated plugin or Jenkins code", - t); - } - } + Listeners.notify(RestartListener.class, true, RestartListener::onRestart); lifecycle.restart(); } catch (InterruptedException | InterruptedIOException e) { LOGGER.log(Level.WARNING, "Interrupted while trying to restart Jenkins", e); @@ -4544,8 +4533,7 @@ public void run() { LOGGER.info("Restart in 10 seconds"); Thread.sleep(TimeUnit.SECONDS.toMillis(10)); LOGGER.info(String.format("Restarting VM as requested by %s",exitUser)); - for (RestartListener listener : RestartListener.all()) - listener.onRestart(); + Listeners.notify(RestartListener.class, true, RestartListener::onRestart); lifecycle.restart(); } else { LOGGER.info("Safe-restart mode cancelled"); @@ -4565,9 +4553,7 @@ public void onRestart() { Computer computer = Jenkins.get().toComputer(); if (computer == null) return; RestartCause cause = new RestartCause(); - for (ComputerListener listener: ComputerListener.all()) { - listener.onOffline(computer, cause); - } + Listeners.notify(ComputerListener.class, true, l -> l.onOffline(computer, cause)); } @Override @@ -4608,7 +4594,7 @@ public void run() { cleanUp(); System.exit(0); - } catch (Exception e) { + } catch (Throwable e) { LOGGER.log(Level.WARNING, "Failed to shut down Jenkins", e); } } @@ -5440,9 +5426,9 @@ public boolean shouldShowStackTrace() { */ public static String VIEW_RESOURCE_PATH = "/resources/TBD"; - @SuppressFBWarnings("MS_SHOULD_BE_FINAL") + @SuppressFBWarnings(value = "MS_SHOULD_BE_FINAL", justification = "for script console") public static boolean PARALLEL_LOAD = SystemProperties.getBoolean(Jenkins.class.getName() + "." + "parallelLoad", true); - @SuppressFBWarnings("MS_SHOULD_BE_FINAL") + @SuppressFBWarnings(value = "MS_SHOULD_BE_FINAL", justification = "for script console") public static boolean KILL_AFTER_LOAD = SystemProperties.getBoolean(Jenkins.class.getName() + "." + "killAfterLoad", false); /** * @deprecated No longer used. @@ -5513,7 +5499,7 @@ public boolean shouldShowStackTrace() { /** * Automatically try to launch an agent when Jenkins is initialized or a new agent computer is created. */ - @SuppressFBWarnings("MS_SHOULD_BE_FINAL") + @SuppressFBWarnings(value = "MS_SHOULD_BE_FINAL", justification = "TODO needs triage") public static boolean AUTOMATIC_SLAVE_LAUNCH = true; private static final Logger LOGGER = Logger.getLogger(Jenkins.class.getName()); @@ -5597,14 +5583,24 @@ public boolean shouldShowStackTrace() { * * @since 2.266 */ - public static final Authentication ANONYMOUS2 = new AnonymousAuthenticationToken("anonymous", "anonymous", Collections.singleton(new SimpleGrantedAuthority("anonymous"))); + public static final Authentication ANONYMOUS2 = + new AnonymousAuthenticationToken( + "anonymous", + "anonymous", + Collections.singleton(new SimpleGrantedAuthority("anonymous"))); /** * @deprecated use {@link #ANONYMOUS2} * @since 1.343 */ @Deprecated - public static final org.acegisecurity.Authentication ANONYMOUS = new org.acegisecurity.providers.anonymous.AnonymousAuthenticationToken("anonymous", "anonymous", new org.acegisecurity.GrantedAuthority[] {new org.acegisecurity.GrantedAuthorityImpl("anonymous")}); + public static final org.acegisecurity.Authentication ANONYMOUS = + new org.acegisecurity.providers.anonymous.AnonymousAuthenticationToken( + "anonymous", + "anonymous", + new org.acegisecurity.GrantedAuthority[] { + new org.acegisecurity.GrantedAuthorityImpl("anonymous"), + }); static { try { diff --git a/core/src/main/java/jenkins/model/NodeListener.java b/core/src/main/java/jenkins/model/NodeListener.java index f54a6dfc2646..091319f08e28 100644 --- a/core/src/main/java/jenkins/model/NodeListener.java +++ b/core/src/main/java/jenkins/model/NodeListener.java @@ -28,8 +28,8 @@ import hudson.ExtensionPoint; import hudson.model.Node; import java.util.List; -import java.util.logging.Level; import java.util.logging.Logger; +import jenkins.util.Listeners; /** * Listen to {@link Node} CRUD operations. @@ -62,13 +62,7 @@ protected void onDeleted(@NonNull Node node) {} * @param node A node being created. */ public static void fireOnCreated(@NonNull Node node) { - for (NodeListener nl: all()) { - try { - nl.onCreated(node); - } catch (Throwable ex) { - LOGGER.log(Level.WARNING, "Listener invocation failed", ex); - } - } + Listeners.notify(NodeListener.class, false, l -> l.onCreated(node)); } /** @@ -78,13 +72,7 @@ public static void fireOnCreated(@NonNull Node node) { * @param newOne New Configuration. */ public static void fireOnUpdated(@NonNull Node oldOne, @NonNull Node newOne) { - for (NodeListener nl: all()) { - try { - nl.onUpdated(oldOne, newOne); - } catch (Throwable ex) { - LOGGER.log(Level.WARNING, "Listener invocation failed", ex); - } - } + Listeners.notify(NodeListener.class, false, l -> l.onUpdated(oldOne, newOne)); } /** @@ -93,13 +81,7 @@ public static void fireOnUpdated(@NonNull Node oldOne, @NonNull Node newOne) { * @param node A node being removed. */ public static void fireOnDeleted(@NonNull Node node) { - for (NodeListener nl: all()) { - try { - nl.onDeleted(node); - } catch (Throwable ex) { - LOGGER.log(Level.WARNING, "Listener invocation failed", ex); - } - } + Listeners.notify(NodeListener.class, false, l -> l.onDeleted(node)); } /** diff --git a/core/src/main/java/jenkins/model/ParameterizedJobMixIn.java b/core/src/main/java/jenkins/model/ParameterizedJobMixIn.java index a5c2a27cda1a..0752b0bcc250 100644 --- a/core/src/main/java/jenkins/model/ParameterizedJobMixIn.java +++ b/core/src/main/java/jenkins/model/ParameterizedJobMixIn.java @@ -132,7 +132,9 @@ public final boolean scheduleBuild(int quietPeriod, Cause c) { * @param job a job which might be schedulable * @param quietPeriod seconds to wait before starting; use {@code -1} to use the job’s default settings * @param actions various actions to associate with the scheduling, such as {@link ParametersAction} or {@link CauseAction} - * @return a newly created, or reused, queue item if the job could be scheduled; null if it was refused for some reason (e.g., some {@link Queue.QueueDecisionHandler} rejected it), or if {@code job} is not a {@link ParameterizedJob} or it is not {@link Job#isBuildable}) + * @return a newly created, or reused, queue item if the job could be scheduled; + * null if it was refused for some reason (e.g., some {@link Queue.QueueDecisionHandler} rejected it), + * or if {@code job} is not a {@link ParameterizedJob} or it is not {@link Job#isBuildable}) * @since 1.621 */ public static @CheckForNull Queue.Item scheduleBuild2(final Job job, int quietPeriod, Action... actions) { diff --git a/core/src/main/java/jenkins/model/RunIdMigrator.java b/core/src/main/java/jenkins/model/RunIdMigrator.java index fea5fbffb6dc..774cc17c7426 100644 --- a/core/src/main/java/jenkins/model/RunIdMigrator.java +++ b/core/src/main/java/jenkins/model/RunIdMigrator.java @@ -283,7 +283,7 @@ static void move(File src, File dest) throws IOException { Files.move(src.toPath(), dest.toPath()); } catch (IOException x) { throw x; - } catch (Exception x) { + } catch (RuntimeException x) { throw new IOException(x); } } diff --git a/core/src/main/java/jenkins/model/StandardArtifactManager.java b/core/src/main/java/jenkins/model/StandardArtifactManager.java index 24cb763a7aa2..195619f70545 100644 --- a/core/src/main/java/jenkins/model/StandardArtifactManager.java +++ b/core/src/main/java/jenkins/model/StandardArtifactManager.java @@ -52,7 +52,7 @@ public class StandardArtifactManager extends ArtifactManager { @Restricted(NoExternalUse.class) @VisibleForTesting - @SuppressFBWarnings("MS_SHOULD_BE_FINAL") + @SuppressFBWarnings(value = "MS_SHOULD_BE_FINAL", justification = "for script console") public static FilePath.TarCompression TAR_COMPRESSION = SystemProperties.getBoolean(StandardArtifactManager.class.getName() + ".disableTrafficCompression") ? FilePath.TarCompression.NONE : FilePath.TarCompression.GZIP; diff --git a/core/src/main/java/jenkins/model/item_category/ItemCategory.java b/core/src/main/java/jenkins/model/item_category/ItemCategory.java index 6165005a6adc..520c98090fa1 100644 --- a/core/src/main/java/jenkins/model/item_category/ItemCategory.java +++ b/core/src/main/java/jenkins/model/item_category/ItemCategory.java @@ -47,7 +47,7 @@ public abstract class ItemCategory implements ExtensionPoint { * See JENKINS-36593 for more info. */ @Restricted(NoExternalUse.class) - @SuppressFBWarnings("MS_SHOULD_BE_FINAL") + @SuppressFBWarnings(value = "MS_SHOULD_BE_FINAL", justification = "TODO needs triage") public static int MIN_TOSHOW = 1; /** diff --git a/core/src/main/java/jenkins/model/lazy/LazyBuildMixIn.java b/core/src/main/java/jenkins/model/lazy/LazyBuildMixIn.java index 23cdb54603b6..8454bf7f676f 100644 --- a/core/src/main/java/jenkins/model/lazy/LazyBuildMixIn.java +++ b/core/src/main/java/jenkins/model/lazy/LazyBuildMixIn.java @@ -43,6 +43,7 @@ import java.io.File; import java.io.IOException; import java.lang.reflect.InvocationTargetException; +import java.util.Objects; import java.util.logging.Level; import java.util.logging.Logger; import jenkins.model.RunIdMigrator; @@ -268,7 +269,8 @@ public final HistoryWidget createHistoryWidget() { */ public interface LazyLoadingJob & Queue.Task & LazyBuildMixIn.LazyLoadingJob, RunT extends Run & LazyLoadingRun> { LazyBuildMixIn getLazyBuildMixIn(); - // not offering default implementation for _getRuns(), removeRun(R), getBuild(String), getBuildByNumber(int), getFirstBuild(), getLastBuild(), getNearestBuild(int), getNearestOldBuild(int), or createHistoryWidget() since they are defined in Job + // not offering default implementation for _getRuns(), removeRun(R), getBuild(String), getBuildByNumber(int), getFirstBuild(), getLastBuild(), getNearestBuild(int), getNearestOldBuild(int), or createHistoryWidget() + // since they are defined in Job // (could allow implementations to call LazyLoadingJob.super.theMethod()) } @@ -361,10 +363,7 @@ public final RunT getPreviousBuild() { if (r == null) { // having two neighbors pointing to each other is important to make RunMap.removeValue work - JobT _parent = asRun().getParent(); - if (_parent == null) { - throw new IllegalStateException("no parent for " + asRun().number); - } + JobT _parent = Objects.requireNonNull(asRun().getParent(), "no parent for " + asRun().number); RunT pb = _parent.getLazyBuildMixIn()._getRuns().search(asRun().number - 1, AbstractLazyLoadRunMap.Direction.DESC); if (pb != null) { pb.getRunMixIn().nextBuildR = createReference(); // establish bi-di link diff --git a/core/src/main/java/jenkins/scm/RunWithSCM.java b/core/src/main/java/jenkins/scm/RunWithSCM.java index 0da5837ff3db..7a3a3394fb11 100644 --- a/core/src/main/java/jenkins/scm/RunWithSCM.java +++ b/core/src/main/java/jenkins/scm/RunWithSCM.java @@ -26,6 +26,7 @@ import edu.umd.cs.findbugs.annotations.CheckForNull; import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import hudson.model.Job; import hudson.model.Result; import hudson.model.Run; @@ -48,6 +49,7 @@ * * @since 2.60 */ +@SuppressFBWarnings(value = "NP_NULL_ON_SOME_PATH_FROM_RETURN_VALUE", justification = "TODO needs triage") public interface RunWithSCM, RunT extends Run & RunWithSCM> { diff --git a/core/src/main/java/jenkins/security/BasicHeaderRealPasswordAuthenticator.java b/core/src/main/java/jenkins/security/BasicHeaderRealPasswordAuthenticator.java index c2ab0e2666b0..600606cde629 100644 --- a/core/src/main/java/jenkins/security/BasicHeaderRealPasswordAuthenticator.java +++ b/core/src/main/java/jenkins/security/BasicHeaderRealPasswordAuthenticator.java @@ -73,6 +73,6 @@ public Authentication authenticate2(HttpServletRequest req, HttpServletResponse * Legacy property to disable the real password support. * Now that this is an extension, {@link ExtensionFilter} is a better way to control this. */ - @SuppressFBWarnings("MS_SHOULD_BE_FINAL") + @SuppressFBWarnings(value = "MS_SHOULD_BE_FINAL", justification = "for script console") public static boolean DISABLE = SystemProperties.getBoolean("jenkins.security.ignoreBasicAuth"); } diff --git a/core/src/main/java/jenkins/security/NonSerializableSecurityContext.java b/core/src/main/java/jenkins/security/NonSerializableSecurityContext.java index 4e47cb20ca8c..334948e7ce12 100644 --- a/core/src/main/java/jenkins/security/NonSerializableSecurityContext.java +++ b/core/src/main/java/jenkins/security/NonSerializableSecurityContext.java @@ -42,7 +42,9 @@ * @see hudson.security.HttpSessionContextIntegrationFilter2 * @since 1.509 */ -@SuppressFBWarnings(value = "SE_TRANSIENT_FIELD_NOT_RESTORED", justification = "It is not intended to be serialized. Default values will be used in case of deserialization") +@SuppressFBWarnings( + value = {"SE_NO_SERIALVERSIONID", "SE_TRANSIENT_FIELD_NOT_RESTORED"}, + justification = "It is not intended to be serialized. Default values will be used in case of deserialization") @Restricted(NoExternalUse.class) public class NonSerializableSecurityContext implements SecurityContext { private transient Authentication authentication; diff --git a/core/src/main/java/jenkins/security/ResourceDomainRootAction.java b/core/src/main/java/jenkins/security/ResourceDomainRootAction.java index 70752dfa0add..e5e83e427138 100644 --- a/core/src/main/java/jenkins/security/ResourceDomainRootAction.java +++ b/core/src/main/java/jenkins/security/ResourceDomainRootAction.java @@ -185,7 +185,7 @@ public Token getToken(@NonNull DirectoryBrowserSupport dbs, @NonNull StaplerRequ try { return new Token(dbsUrl, authenticationName, Instant.now()); - } catch (Exception ex) { + } catch (RuntimeException ex) { LOGGER.log(Level.WARNING, "Failed to encode token for URL: " + dbsUrl + " user: " + authenticationName, ex); } return null; @@ -301,7 +301,7 @@ private static Token decode(String value) { String authenticationName = authenticationNameAndBrowserUrl.substring(0, authenticationNameLength); String browserUrl = authenticationNameAndBrowserUrl.substring(authenticationNameLength + 1); return new Token(browserUrl, authenticationName, ofEpochMilli(Long.parseLong(epoch))); - } catch (Exception ex) { + } catch (RuntimeException ex) { // Choose log level that hides people messing with the URLs LOGGER.log(Level.FINE, "Failure decoding", ex); return null; @@ -313,6 +313,6 @@ private static Token decode(String value) { private static HMACConfidentialKey KEY = new HMACConfidentialKey(ResourceDomainRootAction.class, "key"); // Not @Restricted because the entire class is - @SuppressFBWarnings("MS_SHOULD_BE_FINAL") + @SuppressFBWarnings(value = "MS_SHOULD_BE_FINAL", justification = "for script console") public static /* not final for Groovy */ int VALID_FOR_MINUTES = SystemProperties.getInteger(ResourceDomainRootAction.class.getName() + ".validForMinutes", 30); } diff --git a/core/src/main/java/jenkins/security/apitoken/ApiTokenStore.java b/core/src/main/java/jenkins/security/apitoken/ApiTokenStore.java index 499856ad2970..c898a5422a7b 100644 --- a/core/src/main/java/jenkins/security/apitoken/ApiTokenStore.java +++ b/core/src/main/java/jenkins/security/apitoken/ApiTokenStore.java @@ -26,7 +26,6 @@ import edu.umd.cs.findbugs.annotations.CheckForNull; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; -import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import hudson.Util; import hudson.util.Secret; import java.io.Serializable; @@ -86,7 +85,6 @@ private void init() { } } - @SuppressFBWarnings("NP_NONNULL_RETURN_VIOLATION") public synchronized @NonNull Collection getTokenListSortedByName() { return tokenList.stream() .sorted(SORT_BY_LOWERCASED_NAME) diff --git a/core/src/main/java/jenkins/security/s2m/AdminWhitelistRule.java b/core/src/main/java/jenkins/security/s2m/AdminWhitelistRule.java index 09a236a04d80..98bcac0f7b8b 100644 --- a/core/src/main/java/jenkins/security/s2m/AdminWhitelistRule.java +++ b/core/src/main/java/jenkins/security/s2m/AdminWhitelistRule.java @@ -65,12 +65,16 @@ public AdminWhitelistRule() throws IOException, InterruptedException { // overwrite 30-default.conf with what we think is the best from the core. // this file shouldn't be touched by anyone. For local customization, use other files in the conf dir. // 0-byte file is used as a signal from the admin to prevent this overwriting - placeDefaultRule( - new File(jenkins.getRootDir(), "secrets/whitelisted-callables.d/default.conf"), - getClass().getResourceAsStream("callable.conf")); - placeDefaultRule( - new File(jenkins.getRootDir(), "secrets/filepath-filters.d/30-default.conf"), - transformForWindows(getClass().getResourceAsStream("filepath-filter.conf"))); + try (InputStream callable = getClass().getResourceAsStream("callable.conf")) { + placeDefaultRule( + new File(jenkins.getRootDir(), "secrets/whitelisted-callables.d/default.conf"), + callable); + } + try (InputStream filepathFilter = getClass().getResourceAsStream("filepath-filter.conf")) { + placeDefaultRule( + new File(jenkins.getRootDir(), "secrets/filepath-filters.d/30-default.conf"), + transformForWindows(filepathFilter)); + } this.whitelisted = new CallableWhitelistConfig( new File(jenkins.getRootDir(),"secrets/whitelisted-callables.d/gui.conf")); diff --git a/core/src/main/java/jenkins/security/s2m/CallableDirectionChecker.java b/core/src/main/java/jenkins/security/s2m/CallableDirectionChecker.java index 3c883dbd71c9..5f05307ed059 100644 --- a/core/src/main/java/jenkins/security/s2m/CallableDirectionChecker.java +++ b/core/src/main/java/jenkins/security/s2m/CallableDirectionChecker.java @@ -41,7 +41,7 @@ public class CallableDirectionChecker extends RoleChecker { * This is an escape hatch in case the fix breaks something critical, to allow the user * to keep operation. */ - @SuppressFBWarnings("MS_SHOULD_BE_FINAL") + @SuppressFBWarnings(value = "MS_SHOULD_BE_FINAL", justification = "for script console") public static boolean BYPASS = SystemProperties.getBoolean(BYPASS_PROP); private CallableDirectionChecker(Object context) { diff --git a/core/src/main/java/jenkins/security/s2m/ConfigFile.java b/core/src/main/java/jenkins/security/s2m/ConfigFile.java index 242b694bd6fd..6bec4a8ab3e5 100644 --- a/core/src/main/java/jenkins/security/s2m/ConfigFile.java +++ b/core/src/main/java/jenkins/security/s2m/ConfigFile.java @@ -94,6 +94,10 @@ public synchronized void set(String newContent) throws IOException { } public synchronized void append(String additional) throws IOException { + if (!exists()) { + set(additional); + return; + } String s = read(); if (!s.endsWith("\n")) s += "\n"; diff --git a/core/src/main/java/jenkins/security/s2m/DefaultFilePathFilter.java b/core/src/main/java/jenkins/security/s2m/DefaultFilePathFilter.java index c591827ec8ee..a9ab17d2636b 100644 --- a/core/src/main/java/jenkins/security/s2m/DefaultFilePathFilter.java +++ b/core/src/main/java/jenkins/security/s2m/DefaultFilePathFilter.java @@ -50,7 +50,7 @@ /** * Escape hatch to disable this check completely. */ - @SuppressFBWarnings("MS_SHOULD_BE_FINAL") + @SuppressFBWarnings(value = "MS_SHOULD_BE_FINAL", justification = "for script console") public static boolean BYPASS = SystemProperties.getBoolean(DefaultFilePathFilter.class.getName()+".allow"); private static final Logger LOGGER = Logger.getLogger(DefaultFilePathFilter.class.getName()); diff --git a/core/src/main/java/jenkins/security/s2m/FilePathRuleConfig.java b/core/src/main/java/jenkins/security/s2m/FilePathRuleConfig.java index c4b111f2bdcb..ab0a463d2c5e 100644 --- a/core/src/main/java/jenkins/security/s2m/FilePathRuleConfig.java +++ b/core/src/main/java/jenkins/security/s2m/FilePathRuleConfig.java @@ -74,7 +74,7 @@ protected FilePathRule parse(String line) { Pattern.compile(m.group(3)), createOpMatcher(m.group(2)), m.group(1).equals("allow")); - } catch (Exception e) { + } catch (RuntimeException e) { throw new Failure("Invalid filter rule line: "+line+"\n"+ Functions.printThrowable(e)); } } diff --git a/core/src/main/java/jenkins/security/seed/UserSeedChangeListener.java b/core/src/main/java/jenkins/security/seed/UserSeedChangeListener.java index e928a53e7e25..650beb54c467 100644 --- a/core/src/main/java/jenkins/security/seed/UserSeedChangeListener.java +++ b/core/src/main/java/jenkins/security/seed/UserSeedChangeListener.java @@ -25,12 +25,12 @@ import edu.umd.cs.findbugs.annotations.NonNull; import hudson.ExtensionList; +import hudson.ExtensionPoint; import hudson.model.User; import java.util.List; -import java.util.logging.Level; import java.util.logging.Logger; import jenkins.security.SecurityListener; -import org.apache.tools.ant.ExtensionPoint; +import jenkins.util.Listeners; import org.kohsuke.accmod.Restricted; import org.kohsuke.accmod.restrictions.NoExternalUse; @@ -39,7 +39,7 @@ */ //TODO remove restriction on the weekly after the security fix @Restricted(NoExternalUse.class) -public abstract class UserSeedChangeListener extends ExtensionPoint { +public abstract class UserSeedChangeListener implements ExtensionPoint { private static final Logger LOGGER = Logger.getLogger(SecurityListener.class.getName()); /** @@ -53,14 +53,7 @@ public abstract class UserSeedChangeListener extends ExtensionPoint { * @param user The target user */ public static void fireUserSeedRenewed(@NonNull User user) { - for (UserSeedChangeListener l : all()) { - try { - l.onUserSeedRenewed(user); - } - catch (Exception e) { - LOGGER.log(Level.WARNING, "Exception caught during onUserSeedRenewed event", e); - } - } + Listeners.notify(UserSeedChangeListener.class, true, l -> l.onUserSeedRenewed(user)); } private static List all() { diff --git a/core/src/main/java/jenkins/security/seed/UserSeedProperty.java b/core/src/main/java/jenkins/security/seed/UserSeedProperty.java index 81fc67f07a58..517daa6c3ec2 100644 --- a/core/src/main/java/jenkins/security/seed/UserSeedProperty.java +++ b/core/src/main/java/jenkins/security/seed/UserSeedProperty.java @@ -64,14 +64,14 @@ public class UserSeedProperty extends UserProperty { * If we disable the seed, we can still use it to write / store information but not verifying the data using it. */ @Restricted(NoExternalUse.class) - @SuppressFBWarnings("MS_SHOULD_BE_FINAL") + @SuppressFBWarnings(value = "MS_SHOULD_BE_FINAL", justification = "for script console") public static /* Script Console modifiable */ boolean DISABLE_USER_SEED = SystemProperties.getBoolean(UserSeedProperty.class.getName() + ".disableUserSeed"); /** * Hide the user seed section from the UI to prevent accidental use */ @Restricted(NoExternalUse.class) - @SuppressFBWarnings("MS_SHOULD_BE_FINAL") + @SuppressFBWarnings(value = "MS_SHOULD_BE_FINAL", justification = "for script console") public static /* Script Console modifiable */ boolean HIDE_USER_SEED_SECTION = SystemProperties.getBoolean(UserSeedProperty.class.getName() + ".hideUserSeedSection"); public static final String USER_SESSION_SEED = "_JENKINS_SESSION_SEED"; diff --git a/core/src/main/java/jenkins/security/stapler/StaplerDispatchValidator.java b/core/src/main/java/jenkins/security/stapler/StaplerDispatchValidator.java index fbef121546b5..8e57e8d626c3 100644 --- a/core/src/main/java/jenkins/security/stapler/StaplerDispatchValidator.java +++ b/core/src/main/java/jenkins/security/stapler/StaplerDispatchValidator.java @@ -90,7 +90,7 @@ public class StaplerDispatchValidator implements DispatchValidator { /** * Escape hatch to disable dispatch validation. */ - @SuppressFBWarnings("MS_SHOULD_BE_FINAL") + @SuppressFBWarnings(value = "MS_SHOULD_BE_FINAL", justification = "for script console") public static /* script-console editable */ boolean DISABLED = SystemProperties.getBoolean(ESCAPE_HATCH); private static @CheckForNull Boolean setStatus(@NonNull StaplerRequest req, @CheckForNull Boolean status) { diff --git a/core/src/main/java/jenkins/security/stapler/StaticRoutingDecisionProvider.java b/core/src/main/java/jenkins/security/stapler/StaticRoutingDecisionProvider.java index 889456f3c6ca..ad0a8eb3cbd3 100644 --- a/core/src/main/java/jenkins/security/stapler/StaticRoutingDecisionProvider.java +++ b/core/src/main/java/jenkins/security/stapler/StaticRoutingDecisionProvider.java @@ -263,7 +263,7 @@ private void parseFileIntoList(List lines, Set whitelist, Set - *

  • When the {@link jenkins.slaves.JnlpAgentReceiver#exists(String)} method is invoked for an agent, the {@link jenkins.slaves.JnlpAgentReceiver#owns(String)} method is called on all the extension points: if no owner is found an exception is thrown.
  • + *
  • When the {@link jenkins.slaves.JnlpAgentReceiver#exists(String)} method is invoked for an agent, + * the {@link jenkins.slaves.JnlpAgentReceiver#owns(String)} method is called on all the extension points: + * if no owner is found an exception is thrown.
  • *
  • If owner is found, then the {@link org.jenkinsci.remoting.engine.JnlpConnectionState} lifecycle methods are invoked for all registered {@link JnlpConnectionStateListener} * until the one which changes the state of {@link org.jenkinsci.remoting.engine.JnlpConnectionState} by setting an approval or rejected state is found. * When found, that listener will be set as the owner of the incoming connection event.
  • diff --git a/core/src/main/java/jenkins/slaves/StandardOutputSwapper.java b/core/src/main/java/jenkins/slaves/StandardOutputSwapper.java index 29b3df3fc303..d7e9381f0447 100644 --- a/core/src/main/java/jenkins/slaves/StandardOutputSwapper.java +++ b/core/src/main/java/jenkins/slaves/StandardOutputSwapper.java @@ -66,6 +66,7 @@ private void _swap(StandardOutputStream stdout) throws Exception { } } + @SuppressFBWarnings(value = "OBL_UNSATISFIED_OBLIGATION", justification = "the obligation is satisfied with libc(7)") private void swap(StandardOutputStream stdout) throws IOException, NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException, UnsatisfiedLinkError { // duplicate the OS file descriptor and create FileOutputStream around it int out = GNUCLibrary.LIBC.dup(1); diff --git a/core/src/main/java/jenkins/telemetry/Telemetry.java b/core/src/main/java/jenkins/telemetry/Telemetry.java index 40a50de7a80d..989733f397a1 100644 --- a/core/src/main/java/jenkins/telemetry/Telemetry.java +++ b/core/src/main/java/jenkins/telemetry/Telemetry.java @@ -29,10 +29,12 @@ import hudson.Extension; import hudson.ExtensionList; import hudson.ExtensionPoint; +import hudson.PluginWrapper; import hudson.ProxyConfiguration; import hudson.model.AsyncPeriodicWork; import hudson.model.TaskListener; import hudson.model.UsageStatistics; +import hudson.util.VersionNumber; import java.io.IOException; import java.io.OutputStream; import java.io.OutputStreamWriter; @@ -42,6 +44,8 @@ import java.net.URLConnection; import java.nio.charset.StandardCharsets; import java.time.LocalDate; +import java.util.Map; +import java.util.TreeMap; import java.util.concurrent.TimeUnit; import java.util.logging.Level; import java.util.logging.Logger; @@ -151,6 +155,24 @@ public boolean isActivePeriod() { return now.isAfter(getStart()) && now.isBefore(getEnd()); } + /** + * Produces a list of Jenkins core and plugin version numbers + * to include in telemetry implementations for which this would be relevant. + * @return a map in a format suitable for a value of {@link #createContent} + * @since TODO + */ + protected final Map buildComponentInformation() { + Map components = new TreeMap<>(); + VersionNumber core = Jenkins.getVersion(); + components.put("jenkins-core", core == null ? "" : core.toString()); + for (PluginWrapper plugin : Jenkins.get().pluginManager.getPlugins()) { + if (plugin.isActive()) { + components.put(plugin.getShortName(), plugin.getVersion()); + } + } + return components; + } + @Extension public static class TelemetryReporter extends AsyncPeriodicWork { @@ -182,7 +204,7 @@ protected void execute(TaskListener listener) throws IOException, InterruptedExc JSONObject data = new JSONObject(); try { data = telemetry.createContent(); - } catch (Exception e) { + } catch (RuntimeException e) { LOGGER.log(Level.WARNING, "Failed to build telemetry content for: '" + telemetry.getId() + "'", e); } diff --git a/core/src/main/java/jenkins/telemetry/impl/SlaveToMasterFileCallableUsage.java b/core/src/main/java/jenkins/telemetry/impl/SlaveToMasterFileCallableUsage.java index 3147946f4c8c..2c577d64f5af 100644 --- a/core/src/main/java/jenkins/telemetry/impl/SlaveToMasterFileCallableUsage.java +++ b/core/src/main/java/jenkins/telemetry/impl/SlaveToMasterFileCallableUsage.java @@ -27,8 +27,9 @@ import hudson.Extension; import hudson.Functions; import java.time.LocalDate; -import java.util.Collections; +import java.util.Map; import java.util.Set; +import java.util.TreeMap; import java.util.TreeSet; import java.util.regex.Matcher; import jenkins.SlaveToMasterFileCallable; @@ -64,7 +65,10 @@ public LocalDate getEnd() { @Override public synchronized JSONObject createContent() { - JSONObject json = JSONObject.fromObject(Collections.singletonMap("traces", traces)); + Map info = new TreeMap<>(); + info.put("traces", traces); + info.put("components", buildComponentInformation()); + JSONObject json = JSONObject.fromObject(info); traces.clear(); return json; } diff --git a/core/src/main/java/jenkins/telemetry/impl/StaplerDispatches.java b/core/src/main/java/jenkins/telemetry/impl/StaplerDispatches.java index a21e483f0b65..ab5849499a70 100644 --- a/core/src/main/java/jenkins/telemetry/impl/StaplerDispatches.java +++ b/core/src/main/java/jenkins/telemetry/impl/StaplerDispatches.java @@ -25,15 +25,12 @@ import edu.umd.cs.findbugs.annotations.NonNull; import hudson.Extension; -import hudson.PluginWrapper; -import hudson.util.VersionNumber; import java.time.LocalDate; import java.util.Map; import java.util.Set; import java.util.TreeMap; import java.util.TreeSet; import java.util.concurrent.ConcurrentSkipListSet; -import jenkins.model.Jenkins; import jenkins.telemetry.Telemetry; import net.sf.json.JSONObject; import org.kohsuke.MetaInfServices; @@ -84,19 +81,6 @@ private Object buildDispatches() { return currentTraces; } - private Object buildComponentInformation() { - Map components = new TreeMap<>(); - VersionNumber core = Jenkins.getVersion(); - components.put("jenkins-core", core == null ? "" : core.toString()); - - for (PluginWrapper plugin : Jenkins.get().pluginManager.getPlugins()) { - if (plugin.isActive()) { - components.put(plugin.getShortName(), plugin.getVersion()); - } - } - return components; - } - @MetaInfServices public static class StaplerTrace extends EvaluationTrace.ApplicationTracer { diff --git a/core/src/main/java/jenkins/util/FullDuplexHttpService.java b/core/src/main/java/jenkins/util/FullDuplexHttpService.java index d8c02e002fcd..4eb606d94a73 100644 --- a/core/src/main/java/jenkins/util/FullDuplexHttpService.java +++ b/core/src/main/java/jenkins/util/FullDuplexHttpService.java @@ -59,14 +59,14 @@ public abstract class FullDuplexHttpService { * Set to true if the servlet container doesn't support chunked encoding. */ @Restricted(NoExternalUse.class) - @SuppressFBWarnings("MS_SHOULD_BE_FINAL") + @SuppressFBWarnings(value = "MS_SHOULD_BE_FINAL", justification = "for script console") public static /* Script Console modifiable */ boolean DIY_CHUNKING = SystemProperties.getBoolean("hudson.diyChunking"); /** * Controls the time out of waiting for the 2nd HTTP request to arrive. */ @Restricted(NoExternalUse.class) - @SuppressFBWarnings("MS_SHOULD_BE_FINAL") + @SuppressFBWarnings(value = "MS_SHOULD_BE_FINAL", justification = "for script console") public static /* Script Console modifiable */ long CONNECTION_TIMEOUT = TimeUnit.SECONDS.toMillis(15); protected final UUID uuid; diff --git a/core/src/main/java/jenkins/util/Listeners.java b/core/src/main/java/jenkins/util/Listeners.java new file mode 100644 index 000000000000..4d85c6c5cd8f --- /dev/null +++ b/core/src/main/java/jenkins/util/Listeners.java @@ -0,0 +1,84 @@ +/* + * The MIT License + * + * Copyright 2021 CloudBees, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package jenkins.util; + +import hudson.ExtensionList; +import hudson.security.ACL; +import hudson.security.ACLContext; +import java.util.function.Consumer; +import java.util.logging.Level; +import java.util.logging.Logger; +import org.springframework.security.core.Authentication; + +/** + * Utilities for working with listener interfaces. + * @since TODO + */ +public class Listeners { + + /** + * Safely send a notification to all listeners of a given type. + *

    Only suitable for listener methods which do not throw checked or otherwise documented exceptions. + * @param the type of listener + * @param listenerType the type of listener + * @param asSystem whether to impersonate {@link ACL#SYSTEM2}. + * For most listener methods, this should be {@code true}, + * so that listener implementations can freely perform various operations without access checks. + * In some cases, existing listener interfaces were implicitly assumed to pass along user authentication, + * because they were sometimes triggered by user actions such as configuration changes; + * this is an antipattern (better to pass an explicit {@link Authentication} argument if relevant). + * @param notification a listener method, perhaps with arguments + * @since TODO + */ + public static void notify(Class listenerType, boolean asSystem, Consumer notification) { + Runnable r = () -> { + for (L listener : ExtensionList.lookup(listenerType)) { + try { + notification.accept(listener); + } catch (Throwable x) { + Logger.getLogger(listenerType.getName()).log(Level.WARNING, null, x); + } + } + }; + if (asSystem) { + try (ACLContext ctx = ACL.as2(ACL.SYSTEM2)) { + r.run(); + } + } else { + r.run(); + } + } + + /** + * @deprecated call {@link #notify(Class, boolean, Consumer)} + */ + @Deprecated + public static void notify(Class listenerType, Consumer notification) { + notify(listenerType, true, notification); + } + + private Listeners() {} + +} diff --git a/core/src/main/java/jenkins/util/Timer.java b/core/src/main/java/jenkins/util/Timer.java index 4521312fd8ae..0b2ed74271ae 100644 --- a/core/src/main/java/jenkins/util/Timer.java +++ b/core/src/main/java/jenkins/util/Timer.java @@ -44,7 +44,9 @@ public static synchronized ScheduledExecutorService get() { // corePoolSize is set to 10, but will only be created if needed. // ScheduledThreadPoolExecutor "acts as a fixed-sized pool using corePoolSize threads" // TODO consider also wrapping in ContextResettingExecutorService - executorService = new ImpersonatingScheduledExecutorService(new ErrorLoggingScheduledThreadPoolExecutor(10, new NamingThreadFactory(new ClassLoaderSanityThreadFactory(new DaemonThreadFactory()), "jenkins.util.Timer")), ACL.SYSTEM2); + executorService = new ImpersonatingScheduledExecutorService( + new ErrorLoggingScheduledThreadPoolExecutor(10, new NamingThreadFactory(new ClassLoaderSanityThreadFactory(new DaemonThreadFactory()), "jenkins.util.Timer")), + ACL.SYSTEM2); } return executorService; } diff --git a/core/src/main/java/jenkins/util/io/FileBoolean.java b/core/src/main/java/jenkins/util/io/FileBoolean.java index eae4d0e4b6e3..83ab8a507ac4 100644 --- a/core/src/main/java/jenkins/util/io/FileBoolean.java +++ b/core/src/main/java/jenkins/util/io/FileBoolean.java @@ -59,7 +59,7 @@ public void set(boolean b) { public void on() { try { - file.getParentFile().mkdirs(); + Files.createDirectories(file.getParentFile().toPath()); Files.newOutputStream(file.toPath()).close(); get(); // update state } catch (IOException | InvalidPathException e) { @@ -68,8 +68,12 @@ public void on() { } public void off() { - file.delete(); - get(); // update state + try { + Files.deleteIfExists(file.toPath()); + get(); // update state + } catch (IOException | InvalidPathException e) { + LOGGER.log(Level.WARNING, "Failed to delete " + file); + } } private static final Logger LOGGER = Logger.getLogger(FileBoolean.class.getName()); diff --git a/core/src/main/java/jenkins/websocket/WebSockets.java b/core/src/main/java/jenkins/websocket/WebSockets.java index 5410fba7bb23..91d80c8c5f93 100644 --- a/core/src/main/java/jenkins/websocket/WebSockets.java +++ b/core/src/main/java/jenkins/websocket/WebSockets.java @@ -100,7 +100,12 @@ private synchronized Object init() throws Exception { if (factory == null) { staticInit(); Class webSocketPolicyClass = cl.loadClass("org.eclipse.jetty.websocket.api.WebSocketPolicy"); - factory = cl.loadClass("org.eclipse.jetty.websocket.servlet.WebSocketServletFactory$Loader").getMethod("load", ServletContext.class, webSocketPolicyClass).invoke(null, Stapler.getCurrent().getServletContext(), webSocketPolicyClass.getMethod("newServerPolicy").invoke(null)); + factory = cl.loadClass("org.eclipse.jetty.websocket.servlet.WebSocketServletFactory$Loader") + .getMethod("load", ServletContext.class, webSocketPolicyClass) + .invoke( + null, + Stapler.getCurrent().getServletContext(), + webSocketPolicyClass.getMethod("newServerPolicy").invoke(null)); webSocketServletFactoryClass.getMethod("start").invoke(factory); Class webSocketCreatorClass = cl.loadClass("org.eclipse.jetty.websocket.servlet.WebSocketCreator"); webSocketServletFactoryClass.getMethod("setCreator", webSocketCreatorClass).invoke(factory, Proxy.newProxyInstance(cl, new Class[] {webSocketCreatorClass}, this::createWebSocket)); diff --git a/core/src/main/java/org/acegisecurity/providers/dao/AbstractUserDetailsAuthenticationProvider.java b/core/src/main/java/org/acegisecurity/providers/dao/AbstractUserDetailsAuthenticationProvider.java index 69720e7b4d45..cb55421693be 100644 --- a/core/src/main/java/org/acegisecurity/providers/dao/AbstractUserDetailsAuthenticationProvider.java +++ b/core/src/main/java/org/acegisecurity/providers/dao/AbstractUserDetailsAuthenticationProvider.java @@ -37,9 +37,13 @@ @Deprecated public abstract class AbstractUserDetailsAuthenticationProvider implements AuthenticationProvider { - private final org.springframework.security.authentication.dao.AbstractUserDetailsAuthenticationProvider delegate = new org.springframework.security.authentication.dao.AbstractUserDetailsAuthenticationProvider() { + private final org.springframework.security.authentication.dao.AbstractUserDetailsAuthenticationProvider delegate = + new org.springframework.security.authentication.dao.AbstractUserDetailsAuthenticationProvider() { @Override - protected void additionalAuthenticationChecks(org.springframework.security.core.userdetails.UserDetails userDetails, org.springframework.security.authentication.UsernamePasswordAuthenticationToken authentication) throws org.springframework.security.core.AuthenticationException { + protected void additionalAuthenticationChecks( + org.springframework.security.core.userdetails.UserDetails userDetails, + org.springframework.security.authentication.UsernamePasswordAuthenticationToken authentication) + throws org.springframework.security.core.AuthenticationException { try { AbstractUserDetailsAuthenticationProvider.this.additionalAuthenticationChecks(UserDetails.fromSpring(userDetails), new UsernamePasswordAuthenticationToken(authentication)); } catch (AcegiSecurityException x) { @@ -47,7 +51,10 @@ protected void additionalAuthenticationChecks(org.springframework.security.core. } } @Override - protected org.springframework.security.core.userdetails.UserDetails retrieveUser(String username, org.springframework.security.authentication.UsernamePasswordAuthenticationToken authentication) throws org.springframework.security.core.AuthenticationException { + protected org.springframework.security.core.userdetails.UserDetails retrieveUser( + String username, + org.springframework.security.authentication.UsernamePasswordAuthenticationToken authentication) + throws org.springframework.security.core.AuthenticationException { try { return AbstractUserDetailsAuthenticationProvider.this.retrieveUser(username, new UsernamePasswordAuthenticationToken(authentication)).toSpring(); } catch (AcegiSecurityException x) { diff --git a/core/src/main/resources/hudson/PluginManager/_table.css b/core/src/main/resources/hudson/PluginManager/_table.css deleted file mode 100644 index 5c3bfab4508f..000000000000 --- a/core/src/main/resources/hudson/PluginManager/_table.css +++ /dev/null @@ -1,74 +0,0 @@ -#filter-box { - padding-left: 35px; - width: 15em; -} - -time { - white-space: nowrap; -} - -#filter-container { - margin: 1em 0; - font-size: 1em; -} -.plugin-manager__categories { - margin-top: 0.25em; -} - -.plugin-manager__search-input { - position: static; - padding: 0.25rem 0.1rem 0.25rem 2.5rem; - margin: 0; - - font-size: 1rem; - line-height: 1.5; - color: #333; - color: var(--text-color); - - border-radius: 4px; - border: 1px solid grey; - outline: none; -} - -.plugin-manager__search { - position: relative; -} - -.plugin-manager__icon-leading { - position: absolute; - left: -2px; - top: -3px; - font-size: 15px; - padding: 0.5rem; -} - -.main-search__icon-leading { - left: 0; - pointer-events: none; -} - - -.hidden-by-default { -} -.hidden { - display: none; -} - -#plugins tr.unavailable { - background-color: #f4f4f4; - background-color: var(--plugin-manager-unavailable-bg-color); -} -tr.unavailable span.unavailable-label { - display: inline-block; - border: 1px solid #666; - color: #333; - border-radius: 4px; - font-size: 0.75rem; - font-weight: 500; - padding: 0 0.5rem; - margin: 0.25rem 0.5rem; - text-decoration: none; - text-align: center; - white-space: nowrap; - vertical-align: baseline; -} diff --git a/core/src/main/resources/hudson/PluginManager/_table.js b/core/src/main/resources/hudson/PluginManager/_table.js index 469ac2df6358..d14b436a4a0f 100644 --- a/core/src/main/resources/hudson/PluginManager/_table.js +++ b/core/src/main/resources/hudson/PluginManager/_table.js @@ -12,17 +12,18 @@ Behaviour.specify("#filter-box", '_table', 0, function(e) { function applyFilter() { var filter = e.value.toLowerCase().trim(); var filterParts = filter.split(/ +/).filter (function(word) { return word.length > 0; }); - var items = document.getElementsBySelector("TR.plugin"); + var items = document.getElementsBySelector("TR.plugin").concat(document.getElementsBySelector("TR.unavailable")); var anyVisible = false; for (var i=0; i - - + + + + + + -

    -
    -
    -

    ${%HTTP Proxy Configuration}

    - - - - - - - - - - - - -
    - -
    -

    ${%Deploy Plugin}

    - -
    - ${%deploytext} -
    - - - -

    ${%Or}

    - - - - -
    -
    -
    +
    +

    ${%HTTP Proxy Configuration}

    + + + + + + + + + + + + +
    + + +
    +

    ${%Deploy Plugin}

    + +
    + ${%deploytext} +
    + + + +

    ${%Or}

    + + + + +
    +
    +
    -
    -

    ${%Update Site}

    - - - - - - - - - - - +
    +

    ${%Update Site}

    + + + + + + + + + + + + + + + + +
    + + +
    +

    ${%Other Sites}

    +
      - +
    • ${site.url}
    • -
    - - -
    -

    ${%Other Sites}

    -
      - - -
    • ${site.url}
    • -
      -
      -
    -
    -
    -
    -
    -
    - -
    + + + diff --git a/core/src/main/resources/hudson/PluginManager/available.jelly b/core/src/main/resources/hudson/PluginManager/available.jelly index abcd52b9113f..e22f2f833cc2 100644 --- a/core/src/main/resources/hudson/PluginManager/available.jelly +++ b/core/src/main/resources/hudson/PluginManager/available.jelly @@ -23,47 +23,42 @@ THE SOFTWARE. --> - + + + + + +