diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 40c70094da..b1c034a14f 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -164,6 +164,8 @@ jobs: java: 11 - suite: caffeine:weakKeysAndSoftValuesSyncGuavaTest java: 11 + - suite: caffeine:fuzzTest + java: 11 env: JAVA_VERSION: ${{ matrix.java }} steps: diff --git a/.gitignore b/.gitignore index 36105135e5..b786d47257 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,7 @@ build-cache test-output jitwatch.out .DS_Store +.cifuzz-corpus .classpath .settings .project diff --git a/caffeine/build.gradle.kts b/caffeine/build.gradle.kts index f938a9466a..3796d58143 100644 --- a/caffeine/build.gradle.kts +++ b/caffeine/build.gradle.kts @@ -31,6 +31,7 @@ dependencies { testImplementation(libs.ycsb) { isTransitive = false } + testImplementation(libs.jazzer) testImplementation(libs.picocli) testImplementation(libs.jctools) testImplementation(libs.fastutil) @@ -152,6 +153,18 @@ tasks.register("lincheckTest") { } } +tasks.register("fuzzTest") { + group = "Verification" + description = "Fuzz tests" + + forkEvery = 1 + failFast = true + useJUnitPlatform() + testLogging.events("started") + environment("JAZZER_FUZZ", "1") + include("com/github/benmanes/caffeine/fuzz/**") +} + val junitTest = tasks.register("junitTest") { group = "Verification" description = "JUnit tests" @@ -162,6 +175,7 @@ val junitTest = tasks.register("junitTest") { useJUnit() failFast = true maxHeapSize = "2g" + exclude("com/github/benmanes/caffeine/fuzz/**") systemProperty("caffeine.osgi.jar", relativePath(jar.get().archiveFile.get().asFile.path)) } diff --git a/caffeine/src/main/java/com/github/benmanes/caffeine/cache/CaffeineSpec.java b/caffeine/src/main/java/com/github/benmanes/caffeine/cache/CaffeineSpec.java index 6f280d1bae..08edd68e7f 100644 --- a/caffeine/src/main/java/com/github/benmanes/caffeine/cache/CaffeineSpec.java +++ b/caffeine/src/main/java/com/github/benmanes/caffeine/cache/CaffeineSpec.java @@ -156,7 +156,7 @@ void parseOption(String option) { @SuppressWarnings("StringSplitter") String[] keyAndValue = option.split(SPLIT_KEY_VALUE); - requireArgument(keyAndValue.length <= 2, + requireArgument((keyAndValue.length >= 1) && (keyAndValue.length <= 2), "key-value pair %s with more than one equals sign", option); String key = keyAndValue[0].trim(); diff --git a/caffeine/src/test/java/com/github/benmanes/caffeine/fuzz/CaffeineSpecFuzzer.java b/caffeine/src/test/java/com/github/benmanes/caffeine/fuzz/CaffeineSpecFuzzer.java new file mode 100644 index 0000000000..30f5a411ff --- /dev/null +++ b/caffeine/src/test/java/com/github/benmanes/caffeine/fuzz/CaffeineSpecFuzzer.java @@ -0,0 +1,36 @@ +/* + * Copyright 2024 Ben Manes. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.github.benmanes.caffeine.fuzz; + +import com.code_intelligence.jazzer.api.FuzzedDataProvider; +import com.code_intelligence.jazzer.junit.FuzzTest; +import com.github.benmanes.caffeine.cache.CaffeineSpec; + +/** + * @author ben.manes@gmail.com (Ben Manes) + */ +public final class CaffeineSpecFuzzer { + + // These tests require the environment variable JAZZER_FUZZ=1 to try new input arguments + + @FuzzTest(maxDuration = "5m") + @SuppressWarnings("CheckReturnValue") + public void parse(FuzzedDataProvider data) { + try { + CaffeineSpec.parse(data.consumeRemainingAsString()); + } catch (IllegalArgumentException e) { /* ignored */ } + } +} diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 7060f4fd62..801f927547 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -49,6 +49,7 @@ jakarta-inject = "2.0.1" jamm = "0.4.0" java-object-layout = "0.17" javapoet = "1.13.0" +jazzer = "0.22.1" jcache = "1.1.1" jcommander = "1.82" jctools = "4.0.3" @@ -67,7 +68,7 @@ kotlin = "1.9.23" lincheck = "2.27" mockito = "5.11.0" nexus-publish = "2.0.0-rc-2" -nullaway-core = "0.10.24" +nullaway-core = "0.10.25" nullaway-plugin = "2.0.0" okhttp-bom = "4.12.0" okio-bom = "3.9.0" @@ -82,7 +83,7 @@ protobuf = "4.26.1" slf4j = "2.0.12" slf4j-test = "3.0.1" snakeyaml = "2.2" -sonarqube = "4.4.1.3373" +sonarqube = "5.0.0.4638" spotbugs-contrib = "7.6.4" spotbugs-core = "4.8.3" spotbugs-plugin = "6.0.9" @@ -156,6 +157,7 @@ jakarta-inject = { module = "jakarta.inject:jakarta.inject-api", version.ref = " jamm = { module = "com.github.jbellis:jamm", version.ref = "jamm" } java-object-layout = { module = "org.openjdk.jol:jol-cli", version.ref = "java-object-layout" } javapoet = { module = "com.squareup:javapoet", version.ref = "javapoet" } +jazzer = { module = "com.code-intelligence:jazzer-junit", version.ref = "jazzer" } jcache = { module = "javax.cache:cache-api", version.ref = "jcache" } jcache-guice = { module = "org.jsr107.ri:cache-annotations-ri-guice", version.ref = "jcache" } jcache-tck = { module = "javax.cache:cache-tests", version.ref = "jcache" } diff --git a/gradle/plugins/src/main/kotlin/analyze/jmh-caffeine-conventions.gradle.kts b/gradle/plugins/src/main/kotlin/analyze/jmh-caffeine-conventions.gradle.kts index b29a4940d7..7a094ae9b3 100644 --- a/gradle/plugins/src/main/kotlin/analyze/jmh-caffeine-conventions.gradle.kts +++ b/gradle/plugins/src/main/kotlin/analyze/jmh-caffeine-conventions.gradle.kts @@ -14,6 +14,7 @@ plugins { configurations.jmh { extendsFrom(configurations.testImplementation.get()) + exclude(module = "jazzer-junit") exclude(module = "slf4j-test") } diff --git a/jcache/src/main/java/com/github/benmanes/caffeine/jcache/CacheFactory.java b/jcache/src/main/java/com/github/benmanes/caffeine/jcache/CacheFactory.java index b898dc57e6..5e484c2430 100644 --- a/jcache/src/main/java/com/github/benmanes/caffeine/jcache/CacheFactory.java +++ b/jcache/src/main/java/com/github/benmanes/caffeine/jcache/CacheFactory.java @@ -72,6 +72,7 @@ public static boolean isDefinedExternally(CacheManager cacheManager, String cach * @param cacheName the name of the cache * @return a new cache instance or null if the named cache is not defined in the settings file */ + @SuppressWarnings("resource") public static @Nullable CacheProxy tryToCreateFromExternalSettings( CacheManager cacheManager, String cacheName) { return TypesafeConfigurator.from(rootConfig(cacheManager), cacheName) diff --git a/jcache/src/main/java/com/github/benmanes/caffeine/jcache/CacheProxy.java b/jcache/src/main/java/com/github/benmanes/caffeine/jcache/CacheProxy.java index 3c7d231cd6..07c0241115 100644 --- a/jcache/src/main/java/com/github/benmanes/caffeine/jcache/CacheProxy.java +++ b/jcache/src/main/java/com/github/benmanes/caffeine/jcache/CacheProxy.java @@ -859,8 +859,7 @@ public > C getConfiguration(Class clazz) { } /** Returns the updated expirable value after performing the post-processing actions. */ - @SuppressWarnings({"fallthrough", "NullAway", - "PMD.MissingBreakInSwitch", "PMD.SwitchStmtsShouldHaveDefault"}) + @SuppressWarnings({"fallthrough", "NullAway", "PMD.MissingBreakInSwitch"}) private @Nullable Expirable postProcess(@Nullable Expirable expirable, EntryProcessorEntry entry, long currentTimeMS) { switch (entry.getAction()) { diff --git a/jcache/src/main/java/com/github/benmanes/caffeine/jcache/event/EventDispatcher.java b/jcache/src/main/java/com/github/benmanes/caffeine/jcache/event/EventDispatcher.java index 175d50e194..0059c37493 100644 --- a/jcache/src/main/java/com/github/benmanes/caffeine/jcache/event/EventDispatcher.java +++ b/jcache/src/main/java/com/github/benmanes/caffeine/jcache/event/EventDispatcher.java @@ -239,6 +239,7 @@ private void publish(Cache cache, EventType eventType, K key, JCacheEntryEvent e = event; var dispatchQueue = entry.getValue(); var future = dispatchQueue.compute(key, (k, queue) -> { + @SuppressWarnings("resource") Runnable action = () -> registration.getCacheEntryListener().dispatch(e); return (queue == null) ? CompletableFuture.runAsync(action, executor) diff --git a/jcache/src/main/java/com/github/benmanes/caffeine/jcache/event/EventTypeAwareListener.java b/jcache/src/main/java/com/github/benmanes/caffeine/jcache/event/EventTypeAwareListener.java index ad184dd70b..36773a1440 100644 --- a/jcache/src/main/java/com/github/benmanes/caffeine/jcache/event/EventTypeAwareListener.java +++ b/jcache/src/main/java/com/github/benmanes/caffeine/jcache/event/EventTypeAwareListener.java @@ -47,7 +47,6 @@ public EventTypeAwareListener(CacheEntryListener listener) } /** Returns if the backing listener consumes this type of event. */ - @SuppressWarnings("PMD.SwitchStmtsShouldHaveDefault") public boolean isCompatible(EventType eventType) { switch (eventType) { case CREATED: @@ -63,7 +62,6 @@ public boolean isCompatible(EventType eventType) { } /** Processes the event and logs if an exception is thrown. */ - @SuppressWarnings("PMD.SwitchStmtsShouldHaveDefault") public void dispatch(JCacheEntryEvent event) { try { if (event.getSource().isClosed()) { diff --git a/jcache/src/main/java/com/github/benmanes/caffeine/jcache/event/EventTypeFilter.java b/jcache/src/main/java/com/github/benmanes/caffeine/jcache/event/EventTypeFilter.java index 5cdc055be5..c1a870dd5e 100644 --- a/jcache/src/main/java/com/github/benmanes/caffeine/jcache/event/EventTypeFilter.java +++ b/jcache/src/main/java/com/github/benmanes/caffeine/jcache/event/EventTypeFilter.java @@ -49,7 +49,6 @@ public boolean evaluate(CacheEntryEvent event) { return isCompatible(event) && filter.evaluate(event); } - @SuppressWarnings("PMD.SwitchStmtsShouldHaveDefault") private boolean isCompatible(CacheEntryEvent event) { switch (event.getEventType()) { case CREATED: diff --git a/simulator/src/main/java/com/github/benmanes/caffeine/cache/simulator/policy/sketch/climbing/gradient/Stochastic.java b/simulator/src/main/java/com/github/benmanes/caffeine/cache/simulator/policy/sketch/climbing/gradient/Stochastic.java index 9d835dce74..ea716eaec5 100644 --- a/simulator/src/main/java/com/github/benmanes/caffeine/cache/simulator/policy/sketch/climbing/gradient/Stochastic.java +++ b/simulator/src/main/java/com/github/benmanes/caffeine/cache/simulator/policy/sketch/climbing/gradient/Stochastic.java @@ -60,7 +60,6 @@ public Stochastic(Config config) { } @Override - @SuppressWarnings("PMD.SwitchStmtsShouldHaveDefault") protected double adjust(double hitRate) { double currentMissRate = (1 - hitRate); double previousMissRate = (1 - previousHitRate); diff --git a/simulator/src/main/java/com/github/benmanes/caffeine/cache/simulator/policy/two_queue/TwoQueuePolicy.java b/simulator/src/main/java/com/github/benmanes/caffeine/cache/simulator/policy/two_queue/TwoQueuePolicy.java index 88f9ccf2ee..eed3c14ae8 100644 --- a/simulator/src/main/java/com/github/benmanes/caffeine/cache/simulator/policy/two_queue/TwoQueuePolicy.java +++ b/simulator/src/main/java/com/github/benmanes/caffeine/cache/simulator/policy/two_queue/TwoQueuePolicy.java @@ -111,6 +111,7 @@ public void record(long key) { policyStats.recordHit(); return; } + throw new IllegalStateException("Unknown type: " + node.type); } else { node = new Node(key); node.type = QueueType.IN;