peczenyj/structalign

GitHub: peczenyj/structalign

Go 结构体字段对齐分析工具,以人类可读的 diff 形式展示内存优化建议,支持 CI 集成和字段布局检查。

Stars: 9 | Forks: 0

# structalign [![最新发布](https://img.shields.io/github/release/peczenyj/structalign.svg)](https://github.com/peczenyj/structalign/releases/latest) ![Go 版本](https://img.shields.io/badge/Go-%3E%3D%201.25-%23007d9c) [![GoDoc](https://pkg.go.dev/badge/github.com/peczenyj/structalign)](http://pkg.go.dev/github.com/peczenyj/structalign) [![CI](https://static.pigsec.cn/wp-content/uploads/repos/cas/ad/ad5834178f7599af9fdda11629d49cae07f2997beec49821b2920eff5bfd50e7.svg)](https://github.com/peczenyj/structalign/actions/workflows/ci.yml) [![codecov](https://codecov.io/gh/peczenyj/structalign/graph/badge.svg)](https://codecov.io/gh/peczenyj/structalign) [![报告卡](https://goreportcard.com/badge/github.com/peczenyj/structalign)](https://goreportcard.com/report/github.com/peczenyj/structalign) [![CodeQL](https://static.pigsec.cn/wp-content/uploads/repos/cas/53/539e9a6bf48ad24469a4363bff3aa68124154549e26592783d3d8577f2acbbfc.svg)](https://github.com/peczenyj/structalign/actions/workflows/github-code-scanning/codeql) [![依赖审查](https://static.pigsec.cn/wp-content/uploads/repos/cas/b1/b15cde1db1847f5f456bf59b25038725a992f13ef67da95748c5bbf4ed7fbab7.svg)](https://github.com/peczenyj/structalign/actions/workflows/dependency-review.yml) [![许可证](https://img.shields.io/github/license/peczenyj/structalign)](./LICENSE) [![GitHub 发布日期](https://img.shields.io/github/release-date/peczenyj/structalign.svg)](https://github.com/peczenyj/structalign/releases/latest) [![最近提交](https://img.shields.io/github/last-commit/peczenyj/structalign.svg)](https://github.com/peczenyj/structalign/commit/HEAD) [![欢迎 PR](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](https://github.com/peczenyj/structalign/blob/main/CONTRIBUTING.md#pull-request-process) [![SLSA 构建级别 2](https://img.shields.io/badge/SLSA-Build_L2-green.svg)](https://github.com/peczenyj/structalign/attestations) [![OpenSSF 记分卡](https://api.securityscorecards.dev/projects/github.com/peczenyj/structalign/badge)](https://scorecard.dev/viewer/?uri=github.com/peczenyj/structalign) [![OpenSSF 最佳实践](https://bestpractices.coreinfrastructure.org/projects/13027/badge)](https://bestpractices.coreinfrastructure.org/projects/13027) [![在 Awesome Go 中被提及](https://awesome.re/mentioned-badge-flat.svg)](https://github.com/avelino/awesome-go#code-analysis) `golang.org/x/tools` 的 `fieldalignment` 的只读配套工具:它以专为人工审查而构建的统一或并排 diff 的形式展示内存最优的 struct,而不是重写你的文件或生成机器适用的补丁,它还可以打印任何 struct 的 offset/size/align/padding 布局。该分析直接来自上游分析器,因此结果与 `fieldalignment` 完全匹配——只有展示方式是全新的。 ![structalign 针对内置示例输出的彩色统一 diff](https://static.pigsec.cn/wp-content/uploads/repos/cas/08/080583b65d88e0a3e02e184cfe677e4c2122c2630086b71ba9831c798fe2732e.png) ## 快速开始 安装: ``` go install github.com/peczenyj/structalign@latest ``` 或者从[发布](https://github.com/peczenyj/structalign/releases)页面下载适合你操作系统/架构的预编译二进制文件。使用 `structalign -version` 检查已安装的版本。 然后将其指向一个文件、一个 package 或任何 Go package 模式: ``` structalign ./... # every package in the module ``` 它接受任何 `go` 工具支持的形式——`./...`、导入路径、目录和单个 `.go` 文件——你可以同时传入多个。默认情况下,它会跳过**生成的文件**(`// Code generated … DO NOT EDIT.`)和 `_test.go` 文件;使用 `-generated` / `-tests` 来包含它们(参见[扫描范围](#scanning-scope))。 指向内置示例(`./_example`)时,它会报告重新排序并以非零状态退出,从而可以对 CI 进行卡点: ``` $ structalign -type=Mixed ./_example _example/types.go:6:12: Mixed: struct of size 24 could be 16, saving 8 bytes (33.33% smaller) type Mixed struct { + B int64 A bool - B int64 C bool } $ echo $? 1 ``` ## 为什么会有这个工具 `golang.org/x/tools/.../fieldalignment` 已经能够检测未对齐的 struct 并为你重写它。它提供三种功能: - **report**(默认)——只打印一条简短的消息,如 `struct of size 24 could be 16`,除此之外没有别的; - **`-fix`**——原地重写你的源码; - **`-fix -diff`**——不写入文件,而是以统一补丁的形式打印更改。 所以更改*可以*被展示——但仅仅是作为为 `patch`/`git apply` 构建的补丁,而不是为了让人阅读。它回答了“我该如何应用它?”,而不是“最优的 struct 会是什么样,这种节省值得吗?”而且这些模式都不允许你检查 struct *现有*的布局——offset、size、padding。 `structalign` 是基于同一上游分析的可读性层:它将重新排序展示为面向人类的输出——一种面向审查的 diff(统一或并排格式,带有颜色、摘要、阈值和 tag 剥离)——外加一个字段级的布局检查器。 | | [fieldalignment][fa] | [betteralign][ba] | [structlayout][sl] | **structalign** | |------------------------------|:--:|:--:|:--:|:--:| | 报告未对齐 | ✅ | ✅ | — | ✅ | | **人类可读**的 diff | — | — | — | ✅ | | 机器适用的补丁 | `-fix -diff` | `-fix -diff` | — | — | | 原地重写文件 | `-fix` | `-fix` | — | — | | 检查字段布局 | — | — | ✅ | ✅ | | CI 友好的退出码 | ✅ | ✅ | — | ✅ | ## 用法 `structalign` 是 [`fieldalignment`](https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/fieldalignment)的只读配套工具: 它打印重新排序的 struct 以及一个 diff(或者,使用 `-inspect` 时打印 struct 的内存布局)供审查,从不编辑文件。分析与 `fieldalignment` 完全匹配;如需原地重写,请使用 `fieldalignment -fix`。 `packages` 是任何 go 工具能识别的内容:`./...`、导入路径、目录或单个 `.go` 文件。除非指定了 `-generated` / `-tests`,否则生成的文件和 `_test.go` 文件将被跳过;仅考虑命名的 struct(非空的 `-type` 也会跳过匿名 struct 和 struct 字面量)。 在 diff 模式下,`structalign` 在**发现任何重新排序时退出状态码为 1**,**否则为 0**,因此它可以直接作为 CI 中的一个检查步骤;`-inspect` 总是退出状态码为 0。请注意,最紧凑的排序并不总是最高效的——当心 false sharing(参见 `-skip-cache-padded`)。 ``` structalign ./... # scan every package in the module structalign -diff=side -summary ./... # side-by-side diff plus a total structalign -inspect -type=Config ./pkg # one struct's per-field layout ``` ``` structalign [flags] [packages] packages Go package patterns: ./..., import paths, directories, or single .go files (defaults the go tool understands) -diff value diff style: unified|side|none (default "unified") -format value output format: text|json (default "text") -width int column width per side for -diff=side (default: auto from terminal) -color value colorize: auto|always|never (default "auto") -inspect inspect layout instead of diffing: print each struct as annotated Go source with size/align/padding comments -verbose in -inspect mode, show padding on its own `_` line -tags preserve struct field tags in output (default: strip them) -summary in diff mode, print a one-line summary after the diffs -sort present results largest-first (diff: by bytes saved; inspect: by struct size) -threshold int in diff mode, only show structs that save at least N bytes (default 0; negatives treated as 0) -type string only consider named structs matching these comma-separated glob patterns (e.g. "*Request,Config"); empty means all -exclude string exclude packages whose import path matches this regexp (default "^unsafe$|^builtin$") -generated also analyze generated files (skipped by default) -tests also analyze _test.go files (skipped by default) -skip-cache-padded skip structs with a golang.org/x/sys/cpu.CacheLinePad field -show-nolint show structs even when their type carries a recognized //nolint directive (directives are respected by default) -nolint-linters string //nolint tokens that suppress a finding (default "fieldalignment"; a bare //nolint always counts) -no-rc skip loading .structalignrc files -version print version and exit ``` 在默认的 `-color=auto` 模式下,仅当 stdout 是终端且未设置 [`NO_COLOR`](https://no-color.org) 环境变量时才会输出颜色。`NO_COLOR`(任何非空值)会禁用颜色;显式指定 `-color=always` 会覆盖它。 ### 配置 `structalign` 支持通过环境变量和 `.structalignrc` 文件设置持久化默认值。优先级(最高者胜出): 1. **CLI flags**(例如 `structalign -sort`) 2. **环境变量**:`STRUCTALIGN_`,例如 `STRUCTALIGN_SORT=true`。 3. **本地配置**:当前目录下的 `.structalignrc`。 4. **全局配置**:`~/.structalignrc`。 配置文件使用简单的 `key = value` 格式: ``` # .structalignrc 示例 sort = true threshold = 8 skip-cache-padded = true ``` 键名直接映射到 flag 名称。要跳过加载配置文件(例如在 CI 中),请使用 `-no-rc` flag。请注意,**theme** 不是一个 RC 键;请通过 `STRUCTALIGN_THEME` 环境变量进行设置。 #### 配置参考 | 功能 | CLI Flag | 环境变量 | RC 键 | 默认值 | |---------|----------|----------------------|--------|---------| | Diff 样式 | `-diff` | `STRUCTALIGN_DIFF` | `diff` | `unified` | | 输出格式 | `-format` | `STRUCTALIGN_FORMAT` | `format` | `text` | | 列宽 | `-width` | `STRUCTALIGN_WIDTH` | `width` | `0` (自动) | | 颜色模式 | `-color` | `STRUCTALIGN_COLOR` | `color` | `auto` | | 主题调色板 | — | `STRUCTALIGN_THEME` | — | `default` | | 检查模式 | `-inspect` | `STRUCTALIGN_INSPECT` | `inspect` | `false` | | 详细检查 | `-verbose` | `STRUCTALIGN_VERBOSE` | `verbose` | `false` | | 保留 tags | `-tags` | `STRUCTALIGN_TAGS` | `tags` | `false` | | 显示摘要 | `-summary` | `STRUCTALIGN_SUMMARY` | `summary` | `false` | | 按最大优先排序 | `-sort` | `STRUCTALIGN_SORT` | `sort` | `false` | | 最小节省字节数 | `-threshold` | `STRUCTALIGN_THRESHOLD` | `threshold` | `0` | | 类型过滤器 | `-type` | `STRUCTALIGN_TYPE` | `type` | (空) | | 排除 package | `-exclude` | `STRUCTALIGN_EXCLUDE` | `exclude` | `^unsafe$\|^builtin$` | | 包含生成文件 | `-generated` | `STRUCTALIGN_GENERATED` | `generated` | `false` | | 包含测试文件 | `-tests` | `STRUCTALIGN_TESTS` | `tests` | `false` | | 跳过 cache padded | `-skip-cache-padded` | `STRUCTALIGN_SKIP_CACHE_PADDED` | `skip-cache-padded` | `false` | | 显示 //nolint | `-show-nolint` | `STRUCTALIGN_SHOW_NOLINT` | `show-nolint` | `false` | | Nolint linters | `-nolint-linters` | `STRUCTALIGN_NOLINT_LINTERS` | `nolint-linters` | `fieldalignment` | 可以通过 `STRUCTALIGN_THEME` 环境变量切换调色板 — ... 在第 147 行应用了模糊匹配。 `default`(标准颜色),`cga`(标志性的青色/品红色/白色 CGA 调色板,带有反白标题栏),或者 `green` / `amber`(单色磷光显示器模拟)。它仅影响开启颜色时使用*哪些*颜色;它自身不会开启颜色。未知的取值会发出警告并回退到 `default`。 ## 模式 ### Diff(默认) 统一 diff: ``` $ structalign -type=Mixed ./_example _example/types.go:6:12: Mixed: struct of size 24 could be 16, saving 8 bytes (33.33% smaller) type Mixed struct { + B int64 A bool - B int64 C bool } ``` 并排对比: ``` $ structalign -diff=side -width=28 -type=Mixed ./_example _example/types.go:6:12: Mixed: struct of size 24 could be 16, saving 8 bytes (33.33% smaller) current │ proposed ─────────────────────────────┼───────────────────────────── type Mixed struct { │ type Mixed struct { │ B int64 A bool │ A bool B int64 │ C bool │ C bool } │ } ``` 仅打印重新排序的 struct(无 diff):`structalign -diff=none ./_example`。 使用 `-summary` 时,会在 diff 后追加一行聚合汇总(仅计算已展示的 struct,及其重新排序将节省的字节数): ``` $ structalign -summary ./_example ... (diffs above) ... Summary: 5 structs affected, 56 bytes saved total ``` ### 检查布局 `-inspect` 会完全跳过对齐分析器,并将每个(经过过滤的)命名 struct 打印为带注释的 Go 源码:声明中包含逐个字段的 `// size: N, align: M` 注释,列对齐,并在开头行附加 size/align/padding 摘要。 默认情况下,padding 会折叠到字段注释中: ``` $ structalign -inspect -type=Mixed ./_example type Mixed struct { // size: 24, align: 8, padding: 14 A bool // size: 1, align: 1, padding: 7 B int64 // size: 8, align: 8 C bool // size: 1, align: 1, padding: 7 } ``` 使用 `-verbose` 时,padding 会移至其独立的 `_` 行: ``` $ structalign -inspect -verbose -type=Mixed ./_example type Mixed struct { // size: 24, align: 8, padding: 14 A bool // size: 1, align: 1 _ // 7 byte padding B int64 // size: 8, align: 8 C bool // size: 1, align: 1 _ // 7 byte padding } ``` 该布局来自于 diff 模式所使用的相同 `go/types` 大小计算(`types.Sizes.Offsetsof` / `Sizeof` / `Alignof`),由工具链的目标大小(默认情况下为主机的 `GOOS`/`GOARCH`)驱动。这类似于 [`honnef.co/go/tools/cmd/structlayout`][sl],但它保留在这一个工具内部,并遵循相同的 `-type` 过滤器。 #### 检查泛型类型 泛型 struct **没有单一布局**——`type Box[T any] struct{ … }` 对于每个类型参数的布局都是不同的(`Box[bool]` 和 `Box[[64]byte]` 毫无共同之处),因此没有具体的类型可供度量。因此,inspect 显示的是一种**尽力而为的近似**:每个类型参数都被当作代表性类型进行度量——即其约束的核心类型(例如 `~int` → `int`),或者在约束无限制时使用 `interface{}`(`any`、`comparable`、unions)。字段保留其源码形式(`Value T`,而不是 `Value any`),并且每个其大小依赖于类型参数的字段都会标注其度量时所采用的假设(`-- assume T=any`)。输出也会加上一个免责声明前缀。请将这些数字仅视为指示性的;真实的布局取决于该类型是如何被实例化的。 ``` $ structalign -inspect -type=Generic ./_example // generic type — layout assumes T=any; the real layout depends on the type argument(s) type Generic[T] struct { // size: 32, align: 8, padding: 11 Flag bool // size: 1, align: 1, padding: 7 Value T // size: 16, align: 8 -- assume T=any Count uint32 // size: 4, align: 4, padding: 4 } ``` 一个字段可能会间接地依赖于某个类型参数——通过复合类型或嵌套泛型——标记会随之而来:`map[K]V` 报告 `-- assume K=any, V=any`,而 `Inner[V]` 报告 `-- assume V=any`。 #### 检查不属于自己的类型 structalign 通过 `go/packages` 解析其 package 参数,因此你可以将 `-inspect`( diff 模式)指向不是你编写的类型——只要该 package 可以从**当前目录的 `go.mod`** 中可达。 标准库的 struct 开箱即用——只需提供导入路径和 `-type` 过滤器: ``` $ structalign -inspect -type=Time time type Time struct { // size: 24, align: 8, padding: 0 wall uint64 // size: 8, align: 8 ext int64 // size: 8, align: 8 loc *Location // size: 8, align: 8 } ``` `go.mod` 中已有的依赖项以相同的方式解析: ``` $ structalign -inspect -type=Group golang.org/x/sync/errgroup type Group struct { // size: 64, align: 8, padding: 4 cancel func(error) // size: 8, align: 8 wg sync.WaitGroup // size: 16, align: 8 sem chan token // size: 8, align: 8 errOnce sync.Once // size: 12, align: 4, padding: 4 err error // size: 16, align: 8 } ``` 任何其他库都*必须*被你运行时所在的模块所 *require*——解析是针对当前的 `go.mod` 进行的,**而不是**针对位于 `$GOPATH` 或模块缓存中的任意 package。模块未 require 的 package 将会失败并提示 `no required module provides package …`。检查任意库最快捷的方法是创建一个一次性的临时模块: ``` mkdir /tmp/inspect && cd /tmp/inspect go mod init scratch go get github.com/rs/zerolog structalign -inspect -type=Logger github.com/rs/zerolog ``` 内置的**标量**类型(`int`、`bool`、`string`、……)无法被检查:inspect 打印的是 *struct 字段布局*,而标量没有字段。(出于同样的原因,`builtin` 伪 package 包含在默认的 `-exclude` 中。)要查看标量的大小,请检查包含它的 struct——在 64 位目标机上,一个 `string` 字段会显示 `size: 16`。 ### JSON 输出 `-format=json`(或 `STRUCTALIGN_FORMAT=json`,或 `.structalignrc` 中的 `format = json`)会为 diff 和 inspect 模式输出单一的结构化文档,而不是渲染后的文本。它携带了与文本渲染器所展示的相同数据——发现结果包括 `original` / `proposed`,`oldSize` / `newSize` / `bytesSaved`;inspect 布局包括逐个字段的 `offset` / `size` / `align` / `padding` 以及泛型的 `assume` 注释。 ``` $ structalign -format=json -type=Mixed ./_example { "version": "...", "mode": "diff", "findings": [ ... ], "summary": { "structsAffected": 1, "bytesSaved": 8 } } ``` 出于设计考虑,有两点与文本模式不同: - **diff 文档始终包含 `summary` 块**(以便机器消费端总能获取总计)。`-summary` 仅控制文本渲染器末尾的摘要行。 - **展示类的 flags 不适用。** `-diff`、`-summary`、`-verbose`、`-color` 和 `-width` 仅影响*文本*输出;在 JSON 模式下它们会被忽略,因为消费端会直接从结构化的字段中自行渲染。 `-tags` 仍然适用——它控制是否输出 inspect 文档中逐个字段的 `tag` 字段(参见[字段 tags](#field-tags))。 ### 按类型名称过滤 `-type` 接受一个以逗号分隔的 glob 模式列表(`path.Match` 语法:`*`、`?`、`[...]`),与每个 struct 类型的*声明*名称进行匹配。带有非空过滤器时,永远不会匹配到匿名 struct 和 struct 字面量。它适用于所有模式: ``` structalign -type='*Request' ./... # only structs ending in Request structalign -type='Record,Config' ./pkg # exact names structalign -inspect -type='*ID*' ./pkg # inspect just ID-related structs ``` ### 扫描范围 默认情况下,structalign 会分析每个 package 中常规的、手写的源码。一些 flags 可以调整范围内的内容: ``` structalign -generated ./... # include generated files (skipped by default) structalign -tests ./... # include _test.go files (skipped by default) structalign -exclude='/internal/' ./... # drop packages whose import path matches the regexp structalign -skip-cache-padded ./... # skip structs guarded by cpu.CacheLinePad ``` - 默认情况下会跳过**生成的文件**(`// Code generated … DO NOT EDIT.`)—— 你通常无法手动编辑它们,因此重新排序的建议只会是干扰。 - 默认情况下会跳过 **`_test.go` 文件**;`-tests` 会包含它们。 - **`-exclude`** 接受一个与*导入路径*匹配的正则表达式(默认 `^unsafe$|^builtin$`);它是对匹配 struct 名称的 `-type` 的补充。 - **`-skip-cache-padded`** 会保留带有 [`cpu.CacheLinePad`](https://pkg.go.dev/golang.org/x/sys/cpu#CacheLinePad)字段的 struct 不变,因为重新排序会移动填充并使其失去防范 false sharing 的作用。 - **默认情况下遵守 `//nolint` 指令**(diff 模式):如果 struct 的类型声明带有被认可的 `//nolint`——`//nolint:fieldalignment` 或是单独的 `//nolint`——则会被抑制,这与 golangci-lint 的行为一致。`-nolint-linters` 自定义哪些被命名的 token 会生效(默认为 `fieldalignment`;例如 `-nolint-linters=fieldalignment,betteralign`);单独的 `//nolint` 始终生效。 `-show-nolint` 会显示被抑制的 struct(审计模式)。inspect 模式会忽略这些指令。 ### 字段 tags 默认情况下,该工具会从所有输出中**剥离 struct 字段 tags**,从而将重点放在字段顺序和布局上,而不是 tag 文本上。这在 diff 模式下最为重要:重新排序会改变列宽,这使得 `gofmt` 重新对齐 tags,而这些重新调整间距的更改原本会作为与实际重新排序无关的 diff 噪音出现。从两边剥离 tags 可以消除这种干扰。 传入 `-tags` 以保留 tags。在 diff 模式下,当字段移动时,tags 依然绑定在它们的字段上;在 inspect 模式下,它们被附加在每个字段声明的后面(注释依然保持列对齐): ``` $ structalign -inspect -tags -type=Tagged ./_example type Tagged struct { // size: 48, align: 8, padding: 18 Flag bool `json:"flag"` // size: 1, align: 1, padding: 7 ID string `json:"id" db:"id"` // size: 16, align: 8 Count uint32 `json:"count"` // size: 4, align: 4, padding: 4 Ptr *uint64 // size: 8, align: 8 Enabled bool `json:"enabled"` // size: 1, align: 1, padding: 7 } ``` Tags 永远不会影响布局数字(size/offset/alignment 独立于 tags),因此剥离它们只会改变显示效果,而绝不会影响分析。同样的 flag 也控制着 JSON 输出:在 `-format=json` 模式下,只有当 `-tags`(或 `STRUCTALIGN_TAGS=true`,或 `.structalignrc` 中的 `tags = true`)生效时,inspect 文档的 `tag` 字段才会被输出。 ## 工作原理 `structalign` **没有**重新实现对齐算法。它运行的是**未经修改的** `fieldalignment.Analyzer`,拦截它已经生成的 `analysis.SuggestedFix`(一个单一的 `TextEdit`,用经过最优排序且 gofmt 处理过的版本替换整个 struct 节点),并将其与你的原始源码进行 diff。 因为所有的对齐逻辑——包括 GC 指针字节优化和大小计算——都直接来自上游,所以结果与 `fieldalignment` 完全匹配。只有*展示方式*是全新的。 ## 从源码构建 要求 **Go 1.25+**(由 `golang.org/x/tools` 设定的最低门槛)。该仓库使用 [Task](https://taskfile.dev)([`golangci-lint`](https://golangci-lint.run) 处理 linting 和格式化);`Makefile` 只是委托给 `task`。 ``` git clone https://github.com/peczenyj/structalign cd structalign task build # -> ./structalign (or: go build -o structalign .) task ci # lint, build, test, and a smoke test against ./_example task --list # list all tasks ``` `main.go`(位于模块根目录)是一个轻量级的入口点;具体的实现位于 `pkg/common`(契约)和 `internal/`(loader、align、 layout、ui、app、……)下的小型 package 中。`_example/` 包含用于手动测试的示例 struct——前面的 下划线使得 Go 工具不会将其视为一个 package,因此它不会被包含在 `go build ./...` 及其相关命令中。 ## 继承自 fieldalignment 的注意事项 - 最紧凑的顺序并不总是最高效的——紧实地打包字段 偶尔会引发 goroutine 之间的 false sharing。对于刻意进行 cache-line 填充的 struct,请使用 `-skip-cache-padded`。 - 重新排序可能会破坏逻辑分组/可读性;请将输出视为建议, 这对于频繁分配的热点 struct 最有价值。 - 大小是根据工具链的目标计算的(默认情况下为你的宿主机 `GOOS`/`GOARCH`)。 要分析另一个目标,请在环境变量中设置它们,例如 `GOARCH=386 structalign ./...`。 - 对于**泛型** struct,两种模式都基于类型参数的假定(约束)大小进行工作,因此结果可能与特定的实例化不匹配—— diff 可能会建议非最优的顺序,而 inspect 的数字是近似值(它会打印一份免责声明;参见[检查泛型类型](#inspecting-generic-types))。 ## 设计说明 ### Pipeline 1. 使用 `golang.org/x/tools/go/packages` 加载目标 package(模式 包括语法、类型、类型信息和 `TypesSizes`)。这会像 `go` 工具那样解析 `./...`、 导入路径、目录和单个文件,并从实际的构建目标为分析器提供大小计算数学依据。 2. 通过构建一个 `inspector.New(pkg.Syntax)` 并将其放置在 `Pass.ResultOf` 中,满足分析器唯一的依赖项——`inspect` pass。 3. 提供一个自定义的 `Pass.Report`,它捕获每个诊断的 `NewText`(提议的 struct)并读取 `Pos` 和 `End` 之间的原始源码切片。 4. 使用 `github.com/aymanbagabas/go-udiff`(gopls 使用的 Myers diff package 的一个维护中的独立移植版本,通过 `udiff.Lines`)对两者进行 diff,并将结果渲染为统一或并排的 diff,或者只是打印重新排序后的 struct。 ### 依赖与 internal-package 规则 这个工具存在于它自己的独立模块(`github.com/peczenyj/structalign`)中,并作为普通的 `go get` 可获取的模块拉取两个依赖项: - `golang.org/x/tools`——用于公共的 `.../passes/fieldalignment` 分析器。 - `github.com/aymanbagabas/go-udiff`——用于逐行 diff。 Go 的 internal-package 规则规定,只有当**导入 package 本身的路径**也根植于 `/` 时,该 package 才能导入 `/internal/...`。这就是为什么 diffing 使用 `go-udiff` 而不是 x/tools 自己的 diff package 的原因: - `fieldalignment` 导入了 `golang.org/x/tools/internal/astutil`——这没问题,因为 导入者本身就位于 `golang.org/x/tools/` 之下。这个工具只接触 `fieldalignment` 的公共 API,因此从任何模块导入该分析器都是有效的。 - 相比之下,`golang.org/x/tools/internal/diff` **无法**从 `github.com/peczenyj/structalign`(不位于 `golang.org/x/tools/` 之下)导入,因此编译器会拒绝它。`go-udiff` 是同一段 gopls diff 代码的公开移植版本, 因此结果是等价的。 ## 彩蛋 一些隐藏的 flags 被故意排除在 `-help` 和上面的 flag 表格之外: **`-cga`, `-green`, `-amber`** — 否则需要使用 `STRUCTALIGN_THEME=…` 来选择的复古主题调色板的快捷方式。每次调用只能选择一个;彩蛋 flag 的优先级高于环境变量。 ``` structalign -cga ./... # cyan/magenta/white CGA palette structalign -green -inspect ./_example # single-hue green-phosphor look structalign -amber -diff=side ./... # amber-phosphor side-by-side diff ``` 它们在 flag 解析之前就被剥离了,因此当与常规 flags 结合使用时,它们永远不会触发 *"flag provided but not defined"*。 ## 更新日志 参见 [CHANGELOG.md](CHANGELOG.md)。提交遵循 [Conventional Commits](https://www.conventionalcommits.org),更新日志是使用 [git-cliff](https://git-cliff.org) 以 [Keep a Changelog](https://keepachangelog.com) 格式从提交中生成的: ``` task changelog # regenerate CHANGELOG.md task changelog:unreleased # preview pending entries task release TAG=v0.1.0 # stamp the changelog for a release ``` ## 先前工作 `structalign` 建立在——且受惠于——以下先前的工作: - [**fieldalignment**](https://github.com/golang/tools/tree/master/go/analysis/passes/fieldalignment) 由 Go 作者编写——structalign 包装的上游分析器;所有的对齐数学计算都直接来源于此。 - [**betteralign**](https://github.com/dkorunic/betteralign) 由 Dinko Korunić 编写 —— `fieldalignment` 的一个维护中的继任者,同样会应用修复;structalign 通过 `-nolint-linters` 识别其 `//nolint:betteralign` 指令。 - [**maligned**](https://github.com/mdempsky/maligned) 由 Matthew Dempsky 编写 —— 最初的 struct 字段对齐检测器,现已被 `fieldalignment` 取代。 - [**structslop**](https://github.com/orijtech/structslop) 由 orijtech 编写 —— 建议 进行 struct 字段重排以减少内存占用。 ## 贡献 有关开发工作流、提交约定和发布流程,请参见 [CONTRIBUTING.md](CONTRIBUTING.md)。 ## 许可证 [MIT](LICENSE) © Tiago Peczenyj
标签:EVTX分析, Go, Ruby工具, SOC Prime, 动态分析, 开发工具, 日志审计, 结构体对齐