Skip to content

[Bug?]: Wrong peer dependency warning due to hash collision #6827

Open
@sekyungk

Description

@sekyungk

Self-service

Describe the bug

➜  yarn explain peer-requirements p77551

Package @services/fx-account@workspace:services/fx-account is requested to provide webpack by its descendants

@services/fx-account@workspace:services/fx-account
├─ @sentry/nextjs@npm:7.81.1 [3b802] (via >= 4.0.0)
└─ swc-loader@npm:0.2.6 [dde53] (via >=2)

✘ Package @services/fx-account@workspace:services/fx-account provides webpack with version 5.89.0, which does not satisfy all requests.

As shown above, running yarn explain peer-requirements p77551 produces a warning (✘) even though all peer requirements are actually satisfied.
This happens because PeerRequirementNodes and PeerWarnings are using the same hash (e.g., p77551).
This hash is not unique enough because it only uses the first 5 characters of the generated hash.

As a result, a warning for one node can be incorrectly matched and be displayed for a completely unrelated node.

example:
In Yarn, the peerDependency warning system is handled in the file:

packages/plugin-essentials/sources/commands/explain/peerRequirements.ts

In this file, Yarn checks if there is a warning with the following code:

const warning = project.peerWarnings.find(warning => {
  return warning.hash === peerRequirement.hash;
});

To investigate incorrect warnings from Yarn, I checked both peerWarnings and peerRequirementNodes.

Actual warning object, stored in Project.peerWarnings

{
  "type": 0,
  "subject": {
    "scope": "storybook",
    "name": "addon-controls",
    "locatorHash": "61b34ed9e34c479741834adb88dbc551dbd47391cfd8d80d10ba95c2058f8311e20efd820309a53a01268a901f61187bd9565794d7deba3c2ee5b4935afdd04c",
    "reference": "npm:7.6.20"
    // ... (truncated)
  },
  "requested": {
    "name": "react-dom",
    "range": "^16.8.0 || ^17.0.0 || ^18.0.0"
    // ... (truncated)
  },
  "requester": {
    "scope": "radix-ui",
    "name": "react-dismissable-layer",
    "locatorHash": "469ff45924bb5edad51a5930bb49d282566ee6e260fe7e60f34bb5f6724a6a0eb214b2addbebe9e84bfc0b0aab2d58dd9f0812026efded53f14f5eae297ab5f5"
    // ... (truncated)
  },
  "hash": "p77551"
}

Actual requirement node object, stored in Project.peerRequirementNodes

{
  "subject": {
    "scope": "services",
    "name": "fx-account",
    "locatorHash": "3b802f815f60042a4f3e4d808eeb3c7b3ae7d5c5940768b7e4a6a00c2f6639212318dc44dbd8a5695124638eb65d7a2c6fe8096289e4a6f55d8b817f5d1aeac1",
    "reference": "workspace:services/fx-account"
    // ... (truncated)
  },
  "ident": {
    "name": "webpack",
    "range": ">= 4.0.0"
    // ... (truncated)
  },
  "provided": {
    "name": "webpack",
    "range": "virtual:3b802f815f60042a4f3e4d808eeb3c7b3ae7d5c5940768b7e4a6a00c2f6639212318dc44dbd8a5695124638eb65d7a2c6fe8096289e4a6f55d8b817f5d1aeac1#npm:^5.89.0"
    // ... (truncated)
  },
  "root": true,
  "requests": {},
  "hash": "p77551"
}

As shown in the result above, peerWarnings and peerRequirementNodes are completely different, but they have the same hash(p77551).

So I looked into the hash generator code.

peerWarnings hash generation:

const hash = `p${hashUtils.makeHash(requirement.subject.locatorHash, structUtils.stringifyIdent(requirement.ident), peerRequest.requester.locatorHash).slice(0, 5)}`;

If you plug the peerWarnings object given in the example above into this, the following result comes out.

hashUtils.makeHash(`61b34ed9e34c479741834adb88dbc551dbd47391cfd8d80d10ba95c2058f8311e20efd820309a53a01268a901f61187bd9565794d7deba3c2ee5b4935afdd04c`, `react-dom`, `469ff45924bb5edad51a5930bb49d282566ee6e260fe7e60f34bb5f6724a6a0eb214b2addbebe9e84bfc0b0aab2d58dd9f0812026efded53f14f5eae297ab5f5`)
// 775516f799063a97356f1eb0dd1b0077c8bdffea2a866e0ec48164f635398f91ceda3883a9a54fca8e35cccb87dfcc2b665408853318a12bf0fe7d482ef3f7ed

peerRequirementNodes hash generation:

hash: `p${hashUtils.makeHash(parentLocator.locatorHash, peerDescriptor.identHash).slice(0, 5)}`,

If you plug the peerRequirementNodes object given in the example above into this, the following result comes out.

hashUtils.makeHash(`3b802f815f60042a4f3e4d808eeb3c7b3ae7d5c5940768b7e4a6a00c2f6639212318dc44dbd8a5695124638eb65d7a2c6fe8096289e4a6f55d8b817f5d1aeac1`, `89d6e678e21f2dae30fdf22225647a7491eb218ab40d455b12b1520c7e9e7f77c3eb39e01237d2b6262f8ab528b9d395bcfea571ed877b179e31e5b19fac3983`)
// 7755104b482df1039842fb4892b0de5c130e520ca85ae0bf4f1691f552011859ed0dc3a564ff4261b9b81613279e03a39dd7fdb349bc85df8a865930b6047bfd
  • peerWarnings hash: 775516f799063a97356f1eb0dd1b0077c8bdffea2a866e0ec48164f635398f91ceda3883a9a54fca8e35cccb87dfcc2b665408853318a12bf0fe7d482ef3f7ed
  • peerRequirementNodes hash : 7755104b482df1039842fb4892b0de5c130e520ca85ae0bf4f1691f552011859ed0dc3a564ff4261b9b81613279e03a39dd7fdb349bc85df8a865930b6047bfd

The result is completely different, but if you slice only the first 5 characters, it gives the same value. Because of this, an incorrect peer warning gets matched.

To reproduce

  1. In a project with many peer dependencies, run yarn explain peer-requirements and look for hashes that appear more than once.
  2. Run yarn explain peer-requirements <hash> for one of these hashes where the requirement is actually satisfied.
  3. Observe that a warning (✘) is incorrectly displayed.

Environment

System:
    OS: macOS 14.3.1
    CPU: (10) arm64 Apple M1 Max
  Binaries:
    Node: 20.19.2 - /private/var/folders/q0/df6zb1ks3hd34mgzqwdhlq6hw1nrvx/T/xfs-cf845975/node
    Yarn: 4.9.2-dev - /private/var/folders/q0/df6zb1ks3hd34mgzqwdhlq6hw1nrvx/T/xfs-cf845975/yarn
    npm: 10.8.2 - ~/Library/Caches/fnm_multishells/56147_1750227957146/bin/npm
    bun: 1.2.7 - /opt/homebrew/bin/bun

Additional context

No response

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions