diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/VersionMetadata.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/VersionMetadata.java index 791a45bc35d..7f1b37fdf0b 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/VersionMetadata.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/VersionMetadata.java @@ -33,9 +33,6 @@ public class VersionMetadata implements Comparable { private static final Logger LOG = LoggerFactory.getLogger(VersionMetadata.class); - /** Represents an unknown Besu version in the version metadata file */ - public static final String BESU_VERSION_UNKNOWN = "UNKNOWN"; - private static final String METADATA_FILENAME = "VERSION_METADATA.json"; private static final ObjectMapper MAPPER = new ObjectMapper(); private final String besuVersion; @@ -46,9 +43,7 @@ public class VersionMetadata implements Comparable { * @return the version of Besu */ public static String getRuntimeVersionString() { - return BesuVersionUtils.shortVersion() == null - ? BESU_VERSION_UNKNOWN - : BesuVersionUtils.shortVersion(); + return BesuVersionUtils.shortVersion(); } public static VersionMetadata getRuntimeVersion() { @@ -97,7 +92,7 @@ private static VersionMetadata resolveVersionMetadata(final File metadataFile) versionMetadata = MAPPER.readValue(metadataFile, VersionMetadata.class); LOG.info("Existing version data detected. Besu version {}", versionMetadata.besuVersion); } catch (FileNotFoundException fnfe) { - versionMetadata = new VersionMetadata(BESU_VERSION_UNKNOWN); + versionMetadata = new VersionMetadata(BesuVersionUtils.UNKNOWN); } catch (JsonProcessingException jpe) { throw new IllegalStateException( String.format("Invalid metadata file %s", metadataFile.getAbsolutePath()), jpe); @@ -118,7 +113,7 @@ public static void versionCompatibilityChecks( final boolean enforceCompatibilityProtection, final Path dataDir) throws IOException { final VersionMetadata metadataVersion = VersionMetadata.lookUpFrom(dataDir); final VersionMetadata runtimeVersion = getRuntimeVersion(); - if (metadataVersion.getBesuVersion().equals(VersionMetadata.BESU_VERSION_UNKNOWN)) { + if (metadataVersion.getBesuVersion().equals(BesuVersionUtils.UNKNOWN)) { // The version isn't known, potentially because the file doesn't exist. Write the latest // version to the metadata file. LOG.info( diff --git a/gradle/verification-metadata.xml b/gradle/verification-metadata.xml index 97d10a6be85..87d6c9329d3 100644 --- a/gradle/verification-metadata.xml +++ b/gradle/verification-metadata.xml @@ -1385,6 +1385,16 @@ + + + + + + + + + + @@ -1433,6 +1443,16 @@ + + + + + + + + + + @@ -1912,6 +1932,14 @@ + + + + + + + + @@ -5897,6 +5925,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/util/build.gradle b/util/build.gradle index d874321956c..543ec1ca008 100644 --- a/util/build.gradle +++ b/util/build.gradle @@ -35,6 +35,16 @@ compileJava { '-Alog4j.graalvm.groupId=' + project.group, '-Alog4j.graalvm.artifactId=' + project.name ] + options.errorprone { + check('NullAway', net.ltgt.gradle.errorprone.CheckSeverity.ERROR) + option('NullAway:AnnotatedPackages', 'org.hyperledger.besu.util') + } +} + +tasks.named('compileTestJava', JavaCompile).configure { + options.errorprone { + check('NullAway', net.ltgt.gradle.errorprone.CheckSeverity.OFF) + } } dependencies { @@ -43,6 +53,8 @@ dependencies { annotationProcessor 'org.apache.logging.log4j:log4j-core' annotationProcessor 'com.google.dagger:dagger-compiler' + errorprone 'com.uber.nullaway:nullaway:0.13.1' + implementation 'com.google.guava:guava' implementation 'com.google.dagger:dagger' implementation 'com.github.ben-manes.caffeine:caffeine' @@ -55,6 +67,8 @@ dependencies { implementation 'org.bouncycastle:bcpkix-jdk18on' implementation 'org.xerial.snappy:snappy-java' + compileOnlyApi 'org.jspecify:jspecify' + testImplementation 'org.mockito:mockito-junit-jupiter' testImplementation 'org.assertj:assertj-core' testImplementation 'org.junit.jupiter:junit-jupiter' diff --git a/util/src/main/java/org/hyperledger/besu/util/BesuVersionUtils.java b/util/src/main/java/org/hyperledger/besu/util/BesuVersionUtils.java index 50c8fe377c9..18bcf76faa8 100644 --- a/util/src/main/java/org/hyperledger/besu/util/BesuVersionUtils.java +++ b/util/src/main/java/org/hyperledger/besu/util/BesuVersionUtils.java @@ -33,6 +33,10 @@ */ public final class BesuVersionUtils { private static final Logger LOG = LoggerFactory.getLogger(BesuVersionUtils.class); + + /** Sentinel value used when the version or commit metadata is not available. */ + public static final String UNKNOWN = "UNKNOWN"; + private static final String CLIENT = "besu"; private static final String VERSION; private static final String OS = PlatformDetector.getOS(); @@ -66,8 +70,8 @@ public final class BesuVersionUtils { Optional.ofNullable(implVersion).orElse("NONE/null")); } } - COMMIT = commit; - VERSION = implVersion; + COMMIT = commit != null ? commit : UNKNOWN; + VERSION = implVersion != null ? implVersion : UNKNOWN; } private BesuVersionUtils() {} @@ -75,7 +79,8 @@ private BesuVersionUtils() {} /** * Generate version-only Besu version * - * @return Besu version in format such as "v23.1.0" or "v23.1.1-dev-ac23d311" + * @return Besu version in format such as "v23.1.0" or "v23.1.1-dev-ac23d311", or {@value + * #UNKNOWN} if not available */ public static String shortVersion() { return VERSION; @@ -117,7 +122,7 @@ public static String nodeName(final Optional maybeIdentity) { /** * Generate the commit hash for this besu version * - * @return the commit hash for this besu version + * @return the commit hash for this besu version, or {@value #UNKNOWN} if not available */ public static String commit() { return COMMIT; diff --git a/util/src/main/java/org/hyperledger/besu/util/ExceptionUtils.java b/util/src/main/java/org/hyperledger/besu/util/ExceptionUtils.java index 4232fe1a376..f0a0263b2dc 100644 --- a/util/src/main/java/org/hyperledger/besu/util/ExceptionUtils.java +++ b/util/src/main/java/org/hyperledger/besu/util/ExceptionUtils.java @@ -15,6 +15,7 @@ package org.hyperledger.besu.util; import com.google.common.base.Throwables; +import org.jspecify.annotations.Nullable; /** The Exception utils. */ public class ExceptionUtils { @@ -25,9 +26,9 @@ private ExceptionUtils() {} * Returns the root cause of an exception * * @param throwable the throwable whose root cause we want to find - * @return The root cause + * @return The root cause, or {@code null} if the input is {@code null} */ - public static Throwable rootCause(final Throwable throwable) { + public static @Nullable Throwable rootCause(final @Nullable Throwable throwable) { return throwable != null ? Throwables.getRootCause(throwable) : null; } } diff --git a/util/src/main/java/org/hyperledger/besu/util/cache/MemoryBoundCache.java b/util/src/main/java/org/hyperledger/besu/util/cache/MemoryBoundCache.java index 4102693eb75..b8bfd459e0f 100644 --- a/util/src/main/java/org/hyperledger/besu/util/cache/MemoryBoundCache.java +++ b/util/src/main/java/org/hyperledger/besu/util/cache/MemoryBoundCache.java @@ -16,6 +16,7 @@ import com.github.benmanes.caffeine.cache.Cache; import com.github.benmanes.caffeine.cache.Caffeine; +import org.jspecify.annotations.Nullable; /** * A memory-bound cache that uses Caffeine to limit the size of the cache based on the memory @@ -61,7 +62,7 @@ public void put(final K key, final V value) { * @param key the key whose associated value is to be returned * @return the value associated with the key, or null if not present */ - public V getIfPresent(final K key) { + public @Nullable V getIfPresent(final K key) { return cache.getIfPresent(key); } diff --git a/util/src/main/java/org/hyperledger/besu/util/io/RollingFileWriter.java b/util/src/main/java/org/hyperledger/besu/util/io/RollingFileWriter.java index 81e1e5e8d70..70b0f3c5ca8 100644 --- a/util/src/main/java/org/hyperledger/besu/util/io/RollingFileWriter.java +++ b/util/src/main/java/org/hyperledger/besu/util/io/RollingFileWriter.java @@ -16,10 +16,10 @@ import java.io.Closeable; import java.io.DataOutputStream; -import java.io.File; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; +import java.nio.file.Files; import java.nio.file.Path; import java.util.function.BiFunction; @@ -51,10 +51,16 @@ public RollingFileWriter( currentSize = 0; fileNumber = 0; final Path firstOutputFile = filenameGenerator.apply(fileNumber, compressed); - final File parentDir = firstOutputFile.getParent().toFile(); - if (!parentDir.exists()) { - //noinspection ResultOfMethodCallIgnored - parentDir.mkdirs(); + final Path parentPath = firstOutputFile.getParent(); + if (parentPath != null) { + try { + Files.createDirectories(parentPath); + } catch (final IOException e) { + final FileNotFoundException fnfe = + new FileNotFoundException("Unable to create directory for rolling file: " + parentPath); + fnfe.initCause(e); + throw fnfe; + } } out = new FileOutputStream(firstOutputFile.toFile()); diff --git a/util/src/main/java/org/hyperledger/besu/util/log4j/plugin/StackTraceMatchFilter.java b/util/src/main/java/org/hyperledger/besu/util/log4j/plugin/StackTraceMatchFilter.java index adb5ddc9a86..3deb5406838 100644 --- a/util/src/main/java/org/hyperledger/besu/util/log4j/plugin/StackTraceMatchFilter.java +++ b/util/src/main/java/org/hyperledger/besu/util/log4j/plugin/StackTraceMatchFilter.java @@ -15,6 +15,7 @@ package org.hyperledger.besu.util.log4j.plugin; import java.util.Arrays; +import java.util.Objects; import org.apache.logging.log4j.Level; import org.apache.logging.log4j.Marker; @@ -25,6 +26,7 @@ import org.apache.logging.log4j.core.config.plugins.PluginBuilderFactory; import org.apache.logging.log4j.core.filter.AbstractFilter; import org.apache.logging.log4j.message.Message; +import org.jspecify.annotations.Nullable; /** Matches a text in the stack trace */ @Plugin( @@ -34,11 +36,11 @@ printObject = true) public class StackTraceMatchFilter extends AbstractFilter { private final String stackContains; - private final String messageEquals; + private final @Nullable String messageEquals; private StackTraceMatchFilter( final String stackContains, - final String messageEquals, + final @Nullable String messageEquals, final Result onMatch, final Result onMismatch) { super(onMatch, onMismatch); @@ -71,9 +73,9 @@ public Result filter(final LogEvent event) { return filter(event.getThrown()); } - private Result filter(final Throwable t) { + private Result filter(final @Nullable Throwable t) { if (t != null) { - return (messageEquals == null || t.getMessage().equals(messageEquals)) + return (messageEquals == null || Objects.equals(t.getMessage(), messageEquals)) && Arrays.stream(t.getStackTrace()) .map(StackTraceElement::getClassName) .anyMatch(cn -> cn.contains(stackContains)) @@ -102,8 +104,8 @@ public static StackTraceMatchFilter.Builder newBuilder() { public static class Builder extends AbstractFilterBuilder implements org.apache.logging.log4j.core.util.Builder { - @PluginBuilderAttribute private String stackContains = null; - @PluginBuilderAttribute private String messageEquals = null; + @PluginBuilderAttribute private @Nullable String stackContains; + @PluginBuilderAttribute private @Nullable String messageEquals; /** Default constructor */ public Builder() { @@ -116,7 +118,7 @@ public Builder() { * @param text the match string * @return this builder */ - public StackTraceMatchFilter.Builder setStackContains(final String text) { + public StackTraceMatchFilter.Builder setStackContains(final @Nullable String text) { this.stackContains = text; return this; } @@ -127,15 +129,18 @@ public StackTraceMatchFilter.Builder setStackContains(final String text) { * @param text the match string * @return this builder */ - public StackTraceMatchFilter.Builder setMessageEquals(final String text) { + public StackTraceMatchFilter.Builder setMessageEquals(final @Nullable String text) { this.messageEquals = text; return this; } @Override public StackTraceMatchFilter build() { + final String nonNullStackContains = + Objects.requireNonNull(stackContains, "stackContains must be provided"); + return new StackTraceMatchFilter( - this.stackContains, this.messageEquals, this.getOnMatch(), this.getOnMismatch()); + nonNullStackContains, this.messageEquals, this.getOnMatch(), this.getOnMismatch()); } } } diff --git a/util/src/main/java/org/hyperledger/besu/util/platform/PlatformDetector.java b/util/src/main/java/org/hyperledger/besu/util/platform/PlatformDetector.java index b9d76fc5d67..3b0d816b97f 100644 --- a/util/src/main/java/org/hyperledger/besu/util/platform/PlatformDetector.java +++ b/util/src/main/java/org/hyperledger/besu/util/platform/PlatformDetector.java @@ -27,6 +27,7 @@ import com.sun.jna.Native; import com.sun.jna.ptr.IntByReference; import com.sun.jna.ptr.PointerByReference; +import org.jspecify.annotations.Nullable; /** * Detects OS and VMs. @@ -36,12 +37,12 @@ */ public class PlatformDetector { - private static String _os; - private static String _osType; - private static String _vm; - private static String _arch; - private static String _glibc; - private static String _jemalloc; + private static @Nullable String _os; + private static @Nullable String _osType; + private static @Nullable String _vm; + private static @Nullable String _arch; + private static @Nullable String _glibc; + private static @Nullable String _jemalloc; private PlatformDetector() {} @@ -54,7 +55,7 @@ public static String getOSType() { if (_osType == null) { detect(); } - return _osType; + return _osType == null ? UNKNOWN : _osType; } /** @@ -66,7 +67,7 @@ public static String getOS() { if (_os == null) { detect(); } - return _os; + return _os == null ? UNKNOWN : _os; } /** @@ -78,7 +79,7 @@ public static String getArch() { if (_arch == null) { detect(); } - return _arch; + return _arch == null ? UNKNOWN : _arch; } /** @@ -90,7 +91,7 @@ public static String getVM() { if (_vm == null) { detect(); } - return _vm; + return _vm == null ? UNKNOWN : _vm; } /** @@ -103,7 +104,7 @@ public static String getGlibc() { detectGlibc(); } - return _glibc; + return _glibc == null ? UNKNOWN : _glibc; } /** @@ -117,7 +118,7 @@ public static String getJemalloc() { detectJemalloc(); } - return _jemalloc; + return _jemalloc == null ? UNKNOWN : _jemalloc; } private static final String UNKNOWN = "unknown"; @@ -287,7 +288,11 @@ private static String normalize(final String value) { if (value == null) { return ""; } - return System.getProperty(value).toLowerCase(Locale.US).replaceAll("[^a-z0-9]+", ""); + final String propertyValue = System.getProperty(value); + if (propertyValue == null) { + return ""; + } + return propertyValue.toLowerCase(Locale.US).replaceAll("[^a-z0-9]+", ""); } private static void detectGlibc() { @@ -323,10 +328,10 @@ private static StringBuilder readGlibcVersionStream(final InputStream iStream) } private static String normalizeGLibcVersion(final String rawGlibcVersion) { - final Pattern pattern = Pattern.compile("[-+]?[0-9]*\\.?[0-9]+"); + final Pattern pattern = Pattern.compile("[-+]?[\\d]*\\.?[\\d]+"); final Matcher matcher = pattern.matcher(rawGlibcVersion); - return matcher.find() ? matcher.group() : null; + return matcher.find() ? matcher.group() : UNKNOWN; } private static void detectJemalloc() { @@ -335,7 +340,7 @@ int mallctl( String property, PointerByReference value, IntByReference len, - String newValue, + @Nullable String newValue, int newLen); } diff --git a/util/src/test/java/org/hyperledger/besu/util/BesuVersionUtilsTest.java b/util/src/test/java/org/hyperledger/besu/util/BesuVersionUtilsTest.java index f63638b367d..353c8df20d6 100644 --- a/util/src/test/java/org/hyperledger/besu/util/BesuVersionUtilsTest.java +++ b/util/src/test/java/org/hyperledger/besu/util/BesuVersionUtilsTest.java @@ -17,6 +17,7 @@ import static org.assertj.core.api.Assertions.assertThat; import java.util.Optional; +import java.util.regex.Pattern; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -34,7 +35,10 @@ public final class BesuVersionUtilsTest { @Test public void versionStringIsEthstatsFriendly() { assertThat(BesuVersionUtils.version()) - .matches("[^/]+/v(\\d+\\.\\d+\\.\\d+[^/]*|null)/[^/]+/[^/]+"); + .matches( + "[^/]+/v(\\d+\\.\\d+\\.\\d+[^/]*|" + + Pattern.quote(BesuVersionUtils.UNKNOWN) + + ")/[^/]+/[^/]+"); } /** @@ -46,7 +50,10 @@ public void versionStringIsEthstatsFriendly() { @Test public void noIdentityNodeNameIsEthstatsFriendly() { assertThat(BesuVersionUtils.nodeName(Optional.empty())) - .matches("[^/]+/v(\\d+\\.\\d+\\.\\d+[^/]*|null)/[^/]+/[^/]+"); + .matches( + "[^/]+/v(\\d+\\.\\d+\\.\\d+[^/]*|" + + Pattern.quote(BesuVersionUtils.UNKNOWN) + + ")/[^/]+/[^/]+"); } /** @@ -59,6 +66,9 @@ public void noIdentityNodeNameIsEthstatsFriendly() { @Test public void userIdentityNodeNameIsEthstatsFriendly() { assertThat(BesuVersionUtils.nodeName(Optional.of("TestUserIdentity"))) - .matches("[^/]+/[^/]+/v(\\d+\\.\\d+\\.\\d+[^/]*|null)/[^/]+/[^/]+"); + .matches( + "[^/]+/[^/]+/v(\\d+\\.\\d+\\.\\d+[^/]*|" + + Pattern.quote(BesuVersionUtils.UNKNOWN) + + ")/[^/]+/[^/]+"); } }