Skip to content

[BUG] GM_getValue 返回的是可变对象引用,而不是拷贝 / GM_getValue returns mutable object references instead of copies #1141

Closed

Description

@cyfung1031

@CodFrm https://bbs.tampermonkey.net.cn/thread-9929-1-1.html related

https://greasyfork.org/scripts/529453-twitter-x-media-downloader

问题描述

Summary

在使用 GM_getValue 读取数组或对象时,返回的并不是数据的拷贝,而是对已存储数据的直接引用。因此,对返回对象进行修改(例如对数组调用 push)时,即使没有再次调用 GM_setValue,也会直接影响存储中的值。这种行为很不直观,容易导致开发者误以为修改仅作用于本地变量,从而产生难以排查的隐性 Bug。除非显式对返回值进行深拷贝或将 GM 存储视为不可变数据,否则很容易踩坑。

When using GM_getValue to retrieve arrays or objects, the returned value is a direct reference to the stored data rather than a cloned copy. As a result, mutating the returned object (e.g., calling push on an array) silently modifies the stored value without calling GM_setValue again. This can lead to unexpected side effects where subsequent GM_getValue calls reflect mutations that were assumed to be local. This behavior is non-obvious and can cause hard-to-track bugs unless developers explicitly clone the value or treat GM storage as immutable.

Example

// ==UserScript==
// @name         New Userscript ZLEF-1
// @namespace    https://docs.scriptcat.org/
// @version      0.1.0
// @description  GM_getValue 回传的是可变引用(reference),不是拷贝(copy);直接 mutate 会改到存储 / GM_getValue returns mutable references; mutating it changes storage
// @author       You
// @match        https://example.com/*?zlef*
// @grant        GM_setValue
// @grant        GM_getValue
// @noframes
// ==/UserScript==

function test() {
  // 1) 第一次读取 GM storage 的值
  // 1) First read from GM storage
  let ret1 = GM_getValue("abc");
  console.log(123, ret1);

  // ⚠️ 核心陷阱 / Core pitfall:
  // ret1 不是「拷贝」,而是「指向存储内部资料的引用」
  // ret1 is NOT a cloned copy; it's a reference to the stored object.
  //
  // ✅ 这代表:你对 ret1 做任何「原地修改」(mutate),
  // 即使你没有再呼叫 GM_setValue,也会直接改到 GM storage 里的值。
  // ✅ That means: any in-place mutation on ret1 will silently update GM storage,
  // even without calling GM_setValue again.
  //
  // 例如:push / pop / splice / sort / reverse / obj.xxx = ...
  // e.g. push/pop/splice/sort/reverse / obj.xxx = ...

  ret1.push("test"); // ❗看似改本地,实际上在改存储 / Looks local, actually mutates storage

  // 2) 再次读取 GM storage
  // 2) Read again from GM storage
  let ret2 = GM_getValue("abc");
  console.log(456, ret1, ret2);

  // 3) 第三次读取:用来检查「是不是同一个引用」
  // 3) Third read: check whether it's the same reference
  let ret3 = GM_getValue("abc");

  // 在 ScriptCat 的实作中:通常会得到 true(同一个引用)
  // In ScriptCat implementation: typically true (same reference)
  // 在 Tampermonkey:通常是 false(回传拷贝或不同实例)
  // In Tampermonkey: typically false (copy or different instance)
  console.log(789, ret2 === ret3); // TM: false

  // 结论 / Conclusion:
  // 你 mutate GM_getValue 回传的物件 = mutate 存储本身
  // Mutating objects returned by GM_getValue mutates the stored value itself.
  //
  // 安全做法 / Safe approaches:
  // - 把 GM storage 当成不可变资料(immutable),读出来先 clone 再改
  // - 或者:改完一定要 GM_setValue 回写(并且避免共享引用)
  // - Treat GM storage as immutable: clone before mutate
  // - Or: always GM_setValue after modifications (avoid shared references)
}

// -------------------------------------------------------------------------

(function () {
  "use strict";

  // 当 URL 以 zlef 结尾:仅读取并输出存储值(观察是否被「偷改」)
  // If URL ends with "zlef": just read & log stored value (observe silent mutation)
  if (location.href.endsWith("zlef")) {
    console.log(GM_getValue("abc"));
  } else {
    // 从 URL 取出 zlef 后面的字串
    // Extract substring after "zlef" from URL
    let p = location.href.split("zlef")[1];

    // 建立新阵列并撷取 a-z 片段
    // Create a new array and extract alphabetic substrings
    const arr = [];
    p.replace(/[a-z]+/g, (w) => arr.push(w));

    // 将阵列存入 GM storage
    // Store the array into GM storage
    GM_setValue("abc", arr);

    // 进入测试:会在没有 GM_setValue 的情况下,直接把存储内容改掉
    // Run test: it will mutate the stored value without calling GM_setValue
    test();
  }
})();

// -------------------------------------------------------------------------

Reproduction 重现步骤

脚本猫版本

Both 1.2.x and 1.3.x

操作系统以及浏览器信息

补充信息 (选填)

No response

Activity

  1. added theissue type on Jan 10, 2026
  2. added
    bugSomething isn't working
    P0🚑 需要紧急处理的内容
    hotfix需要尽快更新到扩展商店
    on Jan 10, 2026
  3. CodFrm commented on Jan 16, 2026

    @CodFrm
    Member

    看起来需要做一次深拷贝再把数据返回

  4. CodFrm commented on Jan 16, 2026

    @CodFrm
    Member

    🤔 不知道直接使用 structuredClone 有没有问题 https://developer.mozilla.org/zh-CN/docs/Web/API/Window/structuredClone#%E6%B5%8F%E8%A7%88%E5%99%A8%E5%85%BC%E5%AE%B9%E6%80%A7

    // ==UserScript==
    // @name         New Userscript ZLEF-1
    // @namespace    https://docs.scriptcat.org/
    // @version      0.1.0
    // @description  GM_getValue 回传的是可变引用(reference),不是拷贝(copy);直接 mutate 会改到存储 / GM_getValue returns mutable references; mutating it changes storage
    // @author       You
    // @match        https://example.com/*?zlef*
    // @grant        GM_setValue
    // @grant        GM_getValue
    // @noframes
    // ==/UserScript==
    
    GM_setValue("abc", {
        arr: [1],
        obj: {
            a: "1"
        },
        str: "123",
    });
    
    let value = GM_getValue("abc");
    
    // 浅拷贝
    // let value2 = Object.assign({}, value)
    
    // 深拷贝
    let value2 = structuredClone(value);
    
    value2.arr.push(2);
    
    value2.obj.b = 2;
    
    value2.str = "456";
    
    console.log(value2);
    
    console.log(GM_getValue("abc"));
  5. added a commit that references this issue on Jan 16, 2026
  6. cyfung1031 commented on Jan 16, 2026

    @cyfung1031
    CollaboratorAuthor

    🤔 不知道直接使用 structuredClone 有没有问题

    对喔。有 structuredClone。不用JSON.parse搞来搞去

    JSON.parse 不好的地方是,如果JSON.parse 被脚本hackjack的话,性能不好又有机会循环呼叫

    structuredClone 不会有问题,因为从GM storage 拿出来的是基本物件,不会含有什么proxy 什么symbol 什么 function 那些奇奇怪怪的东西

    效能也比 JSON stringify 再 parse 快得多
    而且 structuredClone 也不会把 object 的 key顺序改变

  7. added a commit that references this issue on Jan 19, 2026
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

    P0🚑 需要紧急处理的内容bugSomething isn't workinghotfix需要尽快更新到扩展商店

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions