Skip to content

Conversation

@fxshlein
Copy link

@fxshlein fxshlein commented Nov 16, 2025

I randomly stumbled across this while playing around with the constraint systems.

Currently, the addConstraint function has a small bug (I think) where it's possible to add lots of duplicate constraints:
image

In this case addConstraint is called with an UPPER(kotlin/Number) constraint. The flow is the following:

  1. Get previous constraints with getConstraintsWithSameTypeHashCode(constraint) and start iterating with the first constraint
  2. The first constraint is from position DeclaredUpperBound, and the new constraint is not, so the newConstraintIsUseless check is skipped
  3. isMatchingForSimplification is true, because we have an existing UPPER and new LOWER constraint
  4. Constraint is simplified to EQUALITY and returned.

The eight other EQUALITY constraints that make the new constraint fully obsolete are never checked. The outcome of this scenario is always to add a new EQUALITY constraint, no matter how often it is repeated. Combined with propagation to other type variables, this can lead to an exponential number of useless constraints being added.

To get this to actually blow up, I had to do a lot of contrived stuff with type parameters and the declared bounds, but I think this might be worth fixing. The simplification logic returns actualConstraint to true, which causes it to always be propagated, so it's presumably a lot more expensive than quickly checking for useless types first. Since the declared upper bound constraints are added early, the chance that the first entry in a constraint is a declared upper bound is pretty high.

The worst case scenario is this: https://gist.github.com/fxshlein/314b8e3fc79d038eefc90e842a810fd4
This manually builds a constraint system and then fixes a type to trigger this bug. But I wasn't able to fully reproduce the list = List<Number> equality constraint through actual kotlin code, so I'm not sure if it is possible to actually crash the compiler using this. Either way its probably worth solving.

I'm not fully sure if/how I should test this. I didn't find any unit tests, and any effect I could produce through code was just a bunch of useless constraints, but no crash. I'd be happy to write a test for this if someone points me to the right place (if there even is one) 😄

Currently, 'addConstraint' will check if the added constraint is
"useless" (i.e., adding an UPPER constraint when EQUAL exists). There
are also special condition where a constraint is never considered
useless, for example when it replaces a constraint from an upper bound.

In the flow of 'addConstraint', if the constraint is not deemed useless,
the next step is checking for whether the previous constraint can be
simplified. If yes, the function immediately returns the simplified
constraint.

Normally, the method would iterate through the list of constraints and
find one that makes the new constraint useless or can be simplified by
it.

But when the first constraint in the list happens to be from an upper
bound, and it can be simplified, then the simplified constraint will
be added to the END of the list. If the same constraint is added again,
instead of skipping it becuase it would be useless, only the first
constraint is checked, and the new constraint is added twice.

This commit instead goes through all constraints first to check if the
constraint is made useless by any of them, and only then tries to
simplify.
@fxshlein fxshlein requested a review from a team as a code owner November 16, 2025 02:05
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