Skip to content

fuzz: add target for FixedSizeCache - #547

Open
brunoerg wants to merge 1 commit into
btcpayserver:masterfrom
brunoerg:fuzzing
Open

fuzz: add target for FixedSizeCache#547
brunoerg wants to merge 1 commit into
btcpayserver:masterfrom
brunoerg:fuzzing

Conversation

@brunoerg

Copy link
Copy Markdown

NBXplorer has no fuzz coverage at all so had to write this from scratch. I'm not a C#/.NET expert so did a quick search about fuzz testing tools for it and noticed that SharpFuzz would be the most suitable for it. The idea of this initial target is to exercise the FixedSizeCache with a fuzzable key: a key whose GetHashCode() is directly controlled by the fuzzer.

@coderabbitai

coderabbitai Bot commented Aug 10, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Added a .NET 10 SharpFuzz executable for FixedSizeCache<TValue, TKey>. It parses bounded fuzz data, generates controlled keys, exercises cache operations, supports edge-case hash codes, and provides replay and out-of-process execution modes.

Changes

FixedSizeCache fuzzing

Layer / File(s) Summary
Fuzz project and key model
NBXplorer.Fuzz/NBXplorer.Fuzz.csproj, NBXplorer.Fuzz/FixedSizeCacheFuzz.cs
Added the .NET 10 fuzzing project and FuzzableKey, which provides controlled hash codes and byte-based equality.
Bounded cache exercise
NBXplorer.Fuzz/FixedSizeCacheFuzz.cs
Added bounded input parsing, deterministic key generation and hashing, and FixedSizeCache add, contains, and remove operations.
Replay and SharpFuzz execution
NBXplorer.Fuzz/FixedSizeCacheFuzz.cs
Added single-input replay and SharpFuzz out-of-process execution. Expected argument exceptions are handled.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Program
  participant SharpFuzz
  participant FixedSizeCacheFuzz
  participant FixedSizeCache
  Program->>Program: inspect command-line arguments
  Program->>SharpFuzz: start out-of-process fuzzing
  SharpFuzz->>FixedSizeCacheFuzz: provide fuzz input
  FixedSizeCacheFuzz->>FixedSizeCache: add, contains, and remove keys
  FixedSizeCache-->>FixedSizeCacheFuzz: cache operation results
Loading

Poem

I’m a rabbit with inputs to run,
Through cache-sized tunnels, one by one.
Hashes leap from low to high,
Add and remove hop nearby.
SharpFuzz hums beneath the sun.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly identifies the addition of a fuzzing target for FixedSizeCache.
Description check ✅ Passed The description explains the SharpFuzz target and its fuzzable key design, which matches the changeset.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Warning

⚠️ This pull request shows signs of AI-generated slop (defensive_cruft, description_diff_mismatch). It has been flagged by CodeRabbit slop detection and should be reviewed carefully.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@NBXplorer.Fuzz/FixedSizeCacheFuzz.cs`:
- Around line 189-205: Remove the ArgumentNullException and
ArgumentOutOfRangeException catch blocks from the Fuzzer.OutOfProcess callback
so exceptions from FixedSizeCacheFuzz.Run propagate to SharpFuzz and failing
inputs are recorded.
- Around line 115-155: Change the cache declaration to use FuzzableKey for both
key and value, with an identity key selector. In the operation switch, pass the
generated key directly to Add, Contains, and Remove instead of constructing and
using the string value, so FuzzableKey.GetHashCode() and Data drive cache
behavior.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 1f9c5618-c0f5-4d21-8b57-dde54a903a2d

📥 Commits

Reviewing files that changed from the base of the PR and between 78305a1 and 4f3af34.

📒 Files selected for processing (2)
  • NBXplorer.Fuzz/FixedSizeCacheFuzz.cs
  • NBXplorer.Fuzz/NBXplorer.Fuzz.csproj

Comment on lines +115 to +155
var cache = new FixedSizeCache<string, FuzzableKey>(
cacheSize,
s => new FuzzableKey(StableHash(s), Encoding.UTF8.GetBytes(s ?? ""))
);

for (int i = 0; i < numOps; i++)
{
if (offset >= data.Length)
break;

int op = ReadByte() % 3;

FuzzableKey key;
bool useEdgeCase = (ReadByte() % 4) == 0;

if (useEdgeCase)
{
int idx = ReadByte() % EdgeCaseHashCodes.Length;
int hc = EdgeCaseHashCodes[idx];
var keyData = ReadBytes(ReadByte() % 32);
key = new FuzzableKey(hc, keyData);
}
else
{
int hc = ReadInt32();
var keyData = ReadBytes(ReadByte() % 28);
key = new FuzzableKey(hc, keyData);
}

string value = $"fuzz_{i}_{key.HashCode}";

switch (op)
{
case 0:
cache.Add(value);
break;
case 1:
cache.Contains(value);
break;
case 2:
cache.Remove(value);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Use FuzzableKey as the cache value and key.

The cache derives its key from StableHash(value). Lines 149, 152, and 155 pass a string value. Therefore, FuzzableKey.GetHashCode() does not control the cache bucket. key.Data also has no effect on the cache operation.

Use FixedSizeCache<FuzzableKey, FuzzableKey> with an identity key selector. Pass key to Add, Contains, and Remove.

Proposed fix
-		var cache = new FixedSizeCache<string, FuzzableKey>(
-			cacheSize,
-			s => new FuzzableKey(StableHash(s), Encoding.UTF8.GetBytes(s ?? ""))
-		);
+		var cache = new FixedSizeCache<FuzzableKey, FuzzableKey>(
+			cacheSize,
+			key => key
+		);
...
-			string value = $"fuzz_{i}_{key.HashCode}";
-
 			switch (op)
 			{
 				case 0:
-					cache.Add(value);
+					cache.Add(key);
 					break;
 				case 1:
-					cache.Contains(value);
+					cache.Contains(key);
 					break;
 				case 2:
-					cache.Remove(value);
+					cache.Remove(key);
 					break;
 			}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
var cache = new FixedSizeCache<string, FuzzableKey>(
cacheSize,
s => new FuzzableKey(StableHash(s), Encoding.UTF8.GetBytes(s ?? ""))
);
for (int i = 0; i < numOps; i++)
{
if (offset >= data.Length)
break;
int op = ReadByte() % 3;
FuzzableKey key;
bool useEdgeCase = (ReadByte() % 4) == 0;
if (useEdgeCase)
{
int idx = ReadByte() % EdgeCaseHashCodes.Length;
int hc = EdgeCaseHashCodes[idx];
var keyData = ReadBytes(ReadByte() % 32);
key = new FuzzableKey(hc, keyData);
}
else
{
int hc = ReadInt32();
var keyData = ReadBytes(ReadByte() % 28);
key = new FuzzableKey(hc, keyData);
}
string value = $"fuzz_{i}_{key.HashCode}";
switch (op)
{
case 0:
cache.Add(value);
break;
case 1:
cache.Contains(value);
break;
case 2:
cache.Remove(value);
var cache = new FixedSizeCache<FuzzableKey, FuzzableKey>(
cacheSize,
key => key
);
for (int i = 0; i < numOps; i++)
{
if (offset >= data.Length)
break;
int op = ReadByte() % 3;
FuzzableKey key;
bool useEdgeCase = (ReadByte() % 4) == 0;
if (useEdgeCase)
{
int idx = ReadByte() % EdgeCaseHashCodes.Length;
int hc = EdgeCaseHashCodes[idx];
var keyData = ReadBytes(ReadByte() % 32);
key = new FuzzableKey(hc, keyData);
}
else
{
int hc = ReadInt32();
var keyData = ReadBytes(ReadByte() % 28);
key = new FuzzableKey(hc, keyData);
}
switch (op)
{
case 0:
cache.Add(key);
break;
case 1:
cache.Contains(key);
break;
case 2:
cache.Remove(key);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@NBXplorer.Fuzz/FixedSizeCacheFuzz.cs` around lines 115 - 155, Change the
cache declaration to use FuzzableKey for both key and value, with an identity
key selector. In the operation switch, pass the generated key directly to Add,
Contains, and Remove instead of constructing and using the string value, so
FuzzableKey.GetHashCode() and Data drive cache behavior.

Comment on lines +189 to +205
Fuzzer.OutOfProcess.Run(stream =>
{
try
{
using var memoryStream = new MemoryStream();
stream.CopyTo(memoryStream);
FixedSizeCacheFuzz.Run(memoryStream.ToArray());
}
catch (ArgumentNullException)
{
// Expected for null inputs
}
catch (ArgumentOutOfRangeException)
{
// Expected for edge-case parameters
}
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Do not suppress exceptions from the fuzz target.

This target creates a positive cache size and passes non-null operation values. ArgumentNullException and ArgumentOutOfRangeException are not expected outcomes for these inputs. The catch blocks hide cache regressions from SharpFuzz.

Remove these handlers so that SharpFuzz records the failing input.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@NBXplorer.Fuzz/FixedSizeCacheFuzz.cs` around lines 189 - 205, Remove the
ArgumentNullException and ArgumentOutOfRangeException catch blocks from the
Fuzzer.OutOfProcess callback so exceptions from FixedSizeCacheFuzz.Run propagate
to SharpFuzz and failing inputs are recorded.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant