Skip to content

refactor: replace private access hacks with accessor pattern#315

Open
zccrs wants to merge 2 commits into
linuxdeepin:masterfrom
zccrs:master
Open

refactor: replace private access hacks with accessor pattern#315
zccrs wants to merge 2 commits into
linuxdeepin:masterfrom
zccrs:master

Conversation

@zccrs
Copy link
Copy Markdown
Member

@zccrs zccrs commented May 25, 2026

Use C++ [temp.explicit]/12 explicit instantiation to bypass access control instead of #define private/protected public hacks.

@zccrs zccrs requested a review from Copilot May 25, 2026 05:32
@deepin-ci-robot
Copy link
Copy Markdown
Contributor

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by: zccrs

The full list of commands accepted by this bot can be found here.

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

Copy link
Copy Markdown

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 removes the #define private/protected public access hack in the platform theme plugin and replaces it with a “private accessor” pattern based on explicit template instantiation ([temp.explicit]/12), and adds CI workflows to build in Deepin/Arch container environments.

Changes:

  • Introduce dprivateaccessor_p.h macros to access private/static Qt internals without preprocessor access hacks.
  • Update qdeepintheme.cpp to use the new accessor macros for QHighDpiScaling private statics across Qt versions.
  • Add GitHub Actions workflows for Deepin crimson and Arch Linux container builds.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
platformthemeplugin/qdeepintheme.cpp Replaces direct private static access with D_PRIVATE_STATIC_MEMBER(...) accessor usage.
platformthemeplugin/dprivateaccessor_p.h Adds the explicit-instantiation-based private member accessor infrastructure.
platformthemeplugin/CMakeLists.txt Registers the new private accessor header in the plugin headers list.
.github/workflows/qt5integration-deepin-build.yml Adds a Deepin crimson container build workflow producing .deb artifacts.
.github/workflows/qt5integration-archlinux-build.yml Adds an Arch container build workflow producing an installation zip artifact.
Comments suppressed due to low confidence (2)

platformthemeplugin/dprivateaccessor_p.h:53

  • dtk_private_detail::access() is declared constexpr but calls get(t), which is not constexpr here. With C++17 this typically makes the function ill-formed (a constexpr function can’t call a non-constexpr function). Either remove constexpr from access() or make the friend get(Tag) declaration/definition constexpr as well.
namespace dtk_private_detail {
    template<typename Tag>
    inline constexpr typename Tag::MemberPtr access(Tag t) noexcept { return get(t); }
}

.github/workflows/qt5integration-deepin-build.yml:51

  • apt-get install -y --allow-unauthenticated wlr-protocols || true both bypasses package authentication and masks installation failures. If the package is optional, prefer checking availability explicitly and keep signature verification enabled; otherwise, remove --allow-unauthenticated and the unconditional || true so failures are actionable.
          set -euxo pipefail
          apt-get install -y --allow-unauthenticated wlr-protocols || true
          cd /tmp
          git clone --depth=1 https://github.com/linuxdeepin/treeland-protocols.git

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +18 to +24
// function is findable via ADL when calling get(TagName{}).

QT_WARNING_PUSH
QT_WARNING_DISABLE_GCC("-Wnon-template-friend")

template<typename Tag>
struct Qt5IntegrationPrivateAccessor
Comment on lines +17 to +20
set -euxo pipefail
echo "deb [trusted=yes] http://mirrors.kernel.org/deepin/beige/ crimson main commercial community" > /etc/apt/sources.list
echo "deb-src [trusted=yes] http://mirrors.kernel.org/deepin/beige/ crimson main commercial community" >> /etc/apt/sources.list
rm -rf /etc/apt/sources.list.d
jobs:
container:
runs-on: ubuntu-latest
container: archlinux:latest
@zccrs zccrs force-pushed the master branch 2 times, most recently from 2a98271 to 0808fb2 Compare May 25, 2026 08:31
Use C++ [temp.explicit]/12 explicit instantiation to bypass access control
instead of #define private/protected public hacks.
tar is always available in base system, no pacman install needed

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@deepin-ci-robot
Copy link
Copy Markdown
Contributor

deepin pr auto review

你好!我是CodeGeeX。我已经仔细审查了你提供的Git Diff,本次修改主要包含三个部分:新增两个CI构建工作流、引入一个用于安全访问私有成员的宏工具集 dprivateaccessor_p.h,以及使用该工具集重构 qdeepintheme.cpp 中对Qt私有变量的访问(移除了危险的 #define private public)。

总体来说,这次修改的方向非常好,特别是移除 #define private public 是提升代码健壮性和合规性的重要改进。但在代码逻辑、性能、安全性和CI配置上,我发现了以下可以改进的地方:

1. 代码逻辑与正确性

1.1 显式模板实例化在头文件中的多重定义风险 (高优先级)
dprivateaccessor_p.h 中的宏 D_DECLARE_PRIVATE_MEMBER 等使用了 template struct Qt5IntegrationPrivateAccessorImpl<TagName, Ptr> 进行显式实例化。如果该头文件被多个 .cpp 文件包含,链接时会产生多重定义错误。

  • 改进建议:将显式实例化声明(extern template)放在头文件中,将显式实例化定义放在某一个 .cpp 文件中;或者更优雅地,利用 inline 变量(C++17)或 constexpr 静态成员来避免显式实例化。如果必须兼容 C++11/14,建议将宏拆分为 D_DECLARE_PRIVATE_... (声明) 和 D_IMPLEMENT_PRIVATE_... (定义,放于cpp)。

1.2 dtk_private_detail::access 绕过 ADL 的逻辑隐患 (中优先级)
注释中提到使用 dtk_private_detail::access 作为跳板是为了防止类作用域内的 get() 抑制 ADL。但是,access 函数内部的 get(t) 依然需要通过 ADL 在 Tag 的命名空间中查找。如果 Tag 定义在全局命名空间或 dtk_private_detail 内部,ADL 可能无法找到作为友元定义在 Qt5IntegrationPrivateAccessor<Tag> 中的 get 函数。

  • 改进建议:确保 Tag 结构体定义在一个独立的命名空间中,或者直接在 access 中通过 Qt5IntegrationPrivateAccessor<Tag>::get(t) 调用(如果将 get 声明为类的静态成员函数会更安全,完全规避 ADL 查找问题)。

1.3 qdeepintheme.cpp 中静态成员指针的类型匹配 (高优先级)

D_DECLARE_PRIVATE_STATIC_MEMBER(QHighDpiScaling_m_logicalDpi_tag, QHighDpiScaling, m_logicalDpi, QHighDpiScaling_m_logicalDpi_type);

m_logicalDpi 在 Qt5 源码中实际是 QPair<qreal, qreal>,而静态成员指针的类型应为 QPair<qreal, qreal>*。宏展开后,MemberPtrQHighDpiScaling_m_logicalDpi_type*,这要求 QHighDpiScaling_m_logicalDpi_type 必须与 QPair<qreal, qreal> 完全一致(不能有 cv 限定差异),否则取地址 &ClassName::MemberName 会编译失败。

  • 改进建议:直接使用 QPair<qreal, qreal> 作为类型参数,或增加类型推导的宏/模板来避免手动指定类型。

2. 代码性能

2.1 CI 工作流中的冗余包安装 (中优先级)
qt5integration-archlinux-build.yml 中,pacman -Syu 已经进行了全系统更新,这会刷新包缓存并更新所有基础库。紧接着的 pacman -S base-devel git cmake ... 可以加上 --needed 参数,避免重复下载和安装已是最新版的包,节约 CI 时间和带宽。

  • 改进建议pacman -S --noconfirm --noprogressbar --needed base-devel git cmake ...

2.2 宏展开带来的运行时开销 (低优先级)
D_PRIVATE_STATIC_MEMBER(tag) 每次访问都会调用 dtk_private_detail::access(tag),虽然现代编译器内联优化后开销极小(等价于直接指针解引用),但在高频调用的渲染或DPI计算路径上,需确保编译器确实进行了内联。

  • 改进建议:在 access 函数声明处显式添加 inline 关键字(你已经加了,这很好),同时建议开启 LTO 优化确保性能无损。

3. 代码安全

3.1 恭喜移除 #define private public (优秀)
这是本次 PR 最大的安全亮点。#define private public 会导致 ODR(One Definition Rule)违背,产生未定义行为(UB),并且在多模块链接时极易引发段错误。使用显式模板实例化技巧是标准合规的,属于合法的“语言律师”级别操作。

3.2 Deepin CI 工作流中的 --allow-unauthenticated (高优先级)

apt-get install -y --allow-unauthenticated wlr-protocols || true

使用 --allow-unauthenticated 会绕过 APT 的包签名验证,容易遭受中间人攻击,在 CI 环境中植入恶意代码。

  • 改进建议:移除 --allow-unauthenticated。如果该包未签名,应该将仓库的 GPG Key 正确导入,或者从源码编译该依赖,而不是妥协安全性。

3.3 CI 工作流中缺少权限限制 (中优先级)
GitHub Actions 默认的 GITHUB_TOKEN 拥有写权限。对于仅构建和上传 Artifact 的工作流,应当遵循最小权限原则。

  • 改进建议:在工作流顶层添加权限限制:
    permissions:
      contents: read

4. 其他改进建议 (格式规范与健壮性)

4.1 宏的安全性增强 (中优先级)
目前的宏没有使用括号保护,且 D_DECLARE_PRIVATE_MEMBER 等宏名没有项目前缀,容易与其他库(如 Qt 自身或 DTK 其他模块)的宏冲突。

  • 改进建议
    1. 将宏名加上项目前缀,例如 QT5INTEG_DECLARE_PRIVATE_MEMBER
    2. 在宏展开时,充分使用括号保护:((obj).*dtk_private_detail::access(tag)) 已经做得不错,但结构体定义中的类型别名也需要注意。

4.2 SPDX 版权年份的自动更新 (低优先级)

-# SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd.
+# SPDX-FileCopyrightText: 2023 - 2026 UnionTech Software Technology Co., Ltd.

写死未来的年份(2026)在开源合规中通常不推荐,因为无法预知代码是否会在 2026 年仍被维护修改。

  • 改进建议:改为 2023 - present 或在当年修改时再更新为 2023 - 2024

4.3 Deepin CI 中的 set -euxo pipefail 位置 (低优先级)
在 Deepin 的 CI 中,set -euxo pipefail 写在了 run 的第一行。这是很好的做法,但 Arch Linux 的 CI 中没有写。建议保持一致性,在 Arch Linux 的 run 块中也加上 set -e,防止命令静默失败。

总结代码修改示例

针对 dprivateaccessor_p.h 的多重定义风险和 ADL 问题,建议重构为如下更安全的形式(利用静态成员函数替代友元函数,彻底规避 ADL 和显式实例化问题):

// dprivateaccessor_p.h
template<typename Tag>
struct Qt5IntegrationPrivateAccessor {
    using MemberPtr = typename Tag::MemberPtr;
    // 使用静态成员函数代替友元函数,无需 ADL,无需显式模板实例化
    static MemberPtr get() noexcept;
};

template<typename Tag, typename Tag::MemberPtr Ptr>
struct Qt5IntegrationPrivateAccessorImpl : Qt5IntegrationPrivateAccessor<Tag> {
    // 特化静态成员函数
    friend typename Tag::MemberPtr Qt5IntegrationPrivateAccessor<Tag>::get() noexcept { return Ptr; }
};

namespace dtk_private_detail {
    template<typename Tag>
    inline typename Tag::MemberPtr access() noexcept { return Qt5IntegrationPrivateAccessor<Tag>::get(); }
}

// 宏定义:不再需要 template struct 显式实例化,头文件包含安全
#define D_DECLARE_PRIVATE_STATIC_MEMBER(TagName, ClassName, MemberName, MemberType) \
    struct TagName { \
        using MemberPtr = MemberType*; \
    }; \
    template <> \
    typename TagName::MemberPtr Qt5IntegrationPrivateAccessor<TagName>::get() noexcept; \
    inline typename TagName::MemberPtr Qt5IntegrationPrivateAccessorImpl<TagName, &ClassName::MemberName>::Qt5IntegrationPrivateAccessor<TagName>::get() noexcept { return &ClassName::MemberName; }

#define D_PRIVATE_STATIC_MEMBER(tag) (*dtk_private_detail::access<decltype(tag)>())

(注:上述代码利用内联特化取代了显式模板实例化,彻底解决了多编译单元链接冲突问题,并去除了对 ADL 的依赖。)

希望这些审查意见对你有所帮助!如果有任何疑问,欢迎随时讨论。

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