Skip to content

Conversation

@simonresch
Copy link
Contributor

@simonresch simonresch commented Oct 23, 2025

When constructing a mutator for a protobuf message the mutator is cached based on the protobuf descriptor. This led to issues for fuzz tests that take two protobuf messages where one message is referring to the other.

While building the mutator for the top level message nested messages are constructed with the type DynamicMessage which lead to 'argument type mismatch' exceptions when invoking the fuzz test method. This is fixed by including the message class in the mutator cache key which ensures that a generated protobuf message class does not re-use the corresponding DynamicMessage mutator.

Note, that this causes some recursion breaking to happen one layer deeper (see test assertion changes).

@simonresch simonresch requested a review from Copilot October 23, 2025 14:19
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull Request Overview

This PR fixes a bug where fuzz tests with multiple protobuf message parameters fail with "argument type mismatch" errors when one message type is a submessage of another. The fix adds the message class to the mutator cache key, preventing incorrect reuse of DynamicMessage mutators for concrete message types.

Changes

  • Modified the CacheKey class to include the message class alongside the descriptor and anySource
  • Added a regression test to verify fuzz test invocation with referring protobufs
  • Re-enabled a previously commented-out fuzz test that was failing due to this bug

Reviewed Changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.

File Description
BuilderMutatorFactory.java Updated CacheKey to include message class in cache key, fixing mutator reuse for nested protobuf messages
ArgumentsMutatorTest.java Added regression test testProtoWithSubmessage() to verify the fix
ArgumentsMutatorFuzzTest.java Re-enabled fuzz_MutuallyReferringProtobufs test that was previously failing

Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

@simonresch simonresch force-pushed the CIF-1811-proto-with-subproto branch from b9432e0 to b7f980a Compare October 23, 2025 14:20
@simonresch simonresch marked this pull request as ready for review October 23, 2025 14:20
@simonresch simonresch requested a review from a team October 23, 2025 14:20
@simonresch simonresch force-pushed the CIF-1811-proto-with-subproto branch 2 times, most recently from 79574a5 to f845260 Compare October 23, 2025 19:37
Copy link
Contributor

@oetr oetr left a comment

Choose a reason for hiding this comment

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

Nice fix!

I think, we could prevent these sort of bugs entirely, if we changed the HashMap internedMutators from global to per fuzz test argument. Like this, cycles in recursive protos will still be broken like before, but the fuzz test arguments will not influence each other's cached internedMutators.

When constructing a mutator for a protobuf message the mutator is cached based
on the protobuf descriptor. This led to issues for fuzz tests that take two
protobuf messages where one message is referring to the other.
While building the mutator for the top level message nested messages are
constructed with the type `DynamicMessage` which lead to 'argument type
mismatch' exceptions when invoking the fuzz test method.
This is fixed by including the message class in the mutator cache key.
@simonresch simonresch force-pushed the CIF-1811-proto-with-subproto branch from f845260 to fbc26c0 Compare October 27, 2025 17:23
@simonresch
Copy link
Contributor Author

I think, we could prevent these sort of bugs entirely, if we changed the HashMap internedMutators from global to per fuzz test argument. Like this, cycles in recursive protos will still be broken like before, but the fuzz test arguments will not influence each other's cached internedMutators.

I was thinking in this direction as well, however I could not find a clean entry point to separate the BuilderMutatorFactory instances by argument. We could adjust the argument/product mutator factory to work on an individual mutator factory per argument, but some mutators could benefit from sharing state across different arguments (maybe CachedConstructorMutatorFactory?).

WDYT, is this worth investigating further? Do you expect any downsides from breaking the recursion one layer deeper?

@oetr
Copy link
Contributor

oetr commented Oct 27, 2025

I think, we could prevent these sort of bugs entirely, if we changed the HashMap internedMutators from global to per fuzz test argument. Like this, cycles in recursive protos will still be broken like before, but the fuzz test arguments will not influence each other's cached internedMutators.

I was thinking in this direction as well, however I could not find a clean entry point to separate the BuilderMutatorFactory instances by argument. We could adjust the argument/product mutator factory to work on an individual mutator factory per argument, but some mutators could benefit from sharing state across different arguments (maybe CachedConstructorMutatorFactory?).

WDYT, is this worth investigating further? Do you expect any downsides from breaking the recursion one layer deeper?

I don't see any downsides, it's a great solution with minimal changes that can and should be merged already!

I am a bit scared of temporary protobuf enum mutators (or others) ending up in internedMutators, and still getting matched when constructing mutators for subsequent args when they shouldn't. Now that you added messageClass to the cache key this shouldn't happen anymore. Right? But I can't tell for sure.

Sharing state between mutators can be done by wiring it though tryCreate, like in the draft PR for the dictionary provider.
The recursion-breaking mutator cache, and all other global data that need to be shared between several mutators, or reset per-argument, can be stored in the new class MutatorRuntime instead of being hidden in some mutator factory.
It's also a nice place to put type-based TORCs 😉

@simonresch simonresch merged commit 707c948 into main Oct 28, 2025
9 checks passed
@simonresch simonresch deleted the CIF-1811-proto-with-subproto branch October 28, 2025 07:18
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.

3 participants