Skip to content

getcolors.js mutates caller-provided frameColor / iconColor / colorMode objects in-place #351

Description

@JohnPHowe

Summary

getcolors.js directly mutates the objects passed in via style.colorMode,
style.frameColor, and style.iconColor rather than working on copies. When a
caller reuses any of those objects across multiple Symbol() calls, mutations
from one render corrupt subsequent renders.

Affected file

src/ms/symbol/getcolors.js

What happens

Lines 3–14 assign the caller's objects to local variables by reference:

let baseFillColor =
  typeof this.style.colorMode === "object"
    ? this.style.colorMode          // ← same object the caller owns
    : ms.getColorMode(this.style.colorMode);
const baseFrameColor =
  typeof this.style.frameColor === "object"
    ? this.style.frameColor         // ← same object the caller owns
    : ms.getColorMode("FrameColor");
const baseIconColor =
  typeof this.style.iconColor === "object"
    ? this.style.iconColor          // ← same object the caller owns
    : ms.getColorMode("IconColor");

Lines 22–48 then mutate those variables directly:

// civilian branch
baseFillColor.Friend = baseFillColor.Neutral = baseFillColor.Unknown = baseFillColor.Civilian;
baseFrameColor.Friend = baseFrameColor.Neutral = baseFrameColor.Unknown = baseFrameColor.Civilian;

// joker/faker branch
baseFillColor.Friend = baseFillColor.Hostile;
baseFrameColor.Friend = baseFrameColor.Hostile;

// suspect branch
baseFillColor.Friend = baseFillColor.Hostile = baseFillColor.Suspect;
baseFrameColor.Friend = baseFrameColor.Hostile = baseFrameColor.Suspect;

// monoColor branch
baseFrameColor.Friend = baseFrameColor.Neutral = baseFrameColor.Hostile = ... = this.style.monoColor;

Because these are in-place property assignments on the original object, the
caller's shared color-mode object is permanently altered after any civilian,
joker, faker, suspect, or monoColor symbol renders.

Reproduction

import ms from 'milsymbol';

const colorMode = ms.getColorMode('Light'); // shared object

// First render: a Suspect symbol — mutates colorMode.Friend
new ms.Symbol('10031500001101000000', { colorMode }).asSVG();

// Second render: a Friend symbol — colorMode.Friend is now corrupted
// to the Suspect value, so this renders with the wrong color
new ms.Symbol('10031000001101000000', { colorMode }).asSVG();

The same issue occurs with any object literal passed as colorMode,
frameColor, or iconColor and reused across calls  a common pattern
when rendering a grid of symbols with a shared color theme.

Fix

Shallow-clone the incoming objects before mutating:

let baseFillColor = {
  ...(typeof this.style.colorMode === "object"
    ? this.style.colorMode
    : ms.getColorMode(this.style.colorMode))
};
const baseFrameColor = {
  ...(typeof this.style.frameColor === "object"
    ? this.style.frameColor
    : ms.getColorMode("FrameColor"))
};
const baseIconColor = {
  ...(typeof this.style.iconColor === "object"
    ? this.style.iconColor
    : ms.getColorMode("IconColor"))
};

One spread per variable; no downstream behavior changes.

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions