audition 0.2.4 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +175 -39
- data/lib/audition/bundle_sweep.rb +32 -15
- data/lib/audition/cli.rb +181 -100
- data/lib/audition/config.rb +33 -7
- data/lib/audition/dynamic/harness.rb +323 -31
- data/lib/audition/dynamic/prober.rb +190 -43
- data/lib/audition/finding.rb +10 -2
- data/lib/audition/progress.rb +418 -0
- data/lib/audition/report/github.rb +69 -0
- data/lib/audition/report/json.rb +68 -0
- data/lib/audition/report/style.rb +74 -0
- data/lib/audition/report/sweep.rb +182 -0
- data/lib/audition/report/text.rb +160 -0
- data/lib/audition/report.rb +16 -295
- data/lib/audition/rewriters.rb +8 -5
- data/lib/audition/static/analyzer.rb +86 -17
- data/lib/audition/static/checks/dependency_class_state.rb +160 -0
- data/lib/audition/static/checks/mutable_constants.rb +162 -28
- data/lib/audition/static/checks/runtime_require.rb +4 -1
- data/lib/audition/static/checks/unsafe_calls.rb +7 -6
- data/lib/audition/static/checks/unshareable_reads.rb +259 -0
- data/lib/audition/static/checks.rb +5 -2
- data/lib/audition/static/gem_calls.rb +1770 -0
- data/lib/audition/static/graph_audit.rb +1053 -12
- data/lib/audition/static/literal_classifier.rb +191 -20
- data/lib/audition/static/native_extensions.rb +175 -0
- data/lib/audition/static/source_file.rb +70 -0
- data/lib/audition/static/work_split.rb +54 -0
- data/lib/audition/target.rb +147 -14
- data/lib/audition/version.rb +1 -1
- data/lib/audition.rb +3 -0
- metadata +15 -4
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 52c12c94d193fa6946553c81ab22b8c21272f269ad4f6f758e99fa4a1c05217e
|
|
4
|
+
data.tar.gz: 858d3ef4e5742ca430337656b9ecea865a89c3f3525ef990073499c8eb438bca
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 60d78e8622cfe35764e7dd30be43ba7e1ceda4f0faf10796230dd6ddb85d952736b345bd380b58823cf97c6687209f6c0fe709193f58326464b61950af1c8a70
|
|
7
|
+
data.tar.gz: 8edc6f7ba79f531317e26f5f1866540ec784d3791db0b6a43ae56d990b22bd173b5234a05618a8b75ba4c5bf1d95bb06a9898c726b493456c36604cdb7c90b66
|
data/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Point it at a Ruby script, a gem, a Rack app, or a Rails root and it
|
|
4
4
|
tells you whether that code can run inside Ractors, why it cannot,
|
|
5
|
-
and how to fix it. Unlike a linter,
|
|
5
|
+
and how to fix it. Unlike a linter, Audition does not stop at
|
|
6
6
|
pattern-matching your source: whole-program analysis is powered by
|
|
7
7
|
[rubydex](https://github.com/Shopify/rubydex), Shopify's Ruby
|
|
8
8
|
indexer, and the target is also loaded in a sandboxed subprocess
|
|
@@ -11,6 +11,10 @@ graph. Some of the checks and fixes were trained on how Rails
|
|
|
11
11
|
core itself is being ractorized; see the
|
|
12
12
|
[pattern study](docs/rails_core_best_practices.md).
|
|
13
13
|
|
|
14
|
+
<div align="center">
|
|
15
|
+
<img src="https://raw.githubusercontent.com/yaroslav/audition/refs/heads/main/assets/images/audition.png" width="512" height="512" alt="gem audition" title="gem audition">
|
|
16
|
+
</div>
|
|
17
|
+
|
|
14
18
|
[](https://github.com/yaroslav/audition/releases)
|
|
15
19
|
[](https://rubydoc.info/gems/audition)
|
|
16
20
|
|
|
@@ -24,8 +28,10 @@ core itself is being ractorized; see the
|
|
|
24
28
|
(which rule of the Ractor model it violates) and a `fix`
|
|
25
29
|
(what to write instead).
|
|
26
30
|
- **`--fix` like RuboCop, in two tiers.** Safe corrections:
|
|
27
|
-
`.freeze` on string constants,
|
|
28
|
-
|
|
31
|
+
`.freeze` on string constants, sentinels, and containers whose
|
|
32
|
+
elements are all shareable, `Ractor.make_shareable(...)` for
|
|
33
|
+
the remaining mutable and shallow-frozen containers and for
|
|
34
|
+
Proc constants, and
|
|
29
35
|
boot-time hoisting of method-body requires. `--fix-unsafe` adds
|
|
30
36
|
semantics-affecting rewrites: magic-comment insertion,
|
|
31
37
|
freeze-on-memoize for class-level memoization (both `@x ||=`
|
|
@@ -41,35 +47,35 @@ core itself is being ractorized; see the
|
|
|
41
47
|
state, so `globalid` is not blamed for ActiveSupport's state.
|
|
42
48
|
- **Dogfooding.** The scanner for Ractor compatibility is built
|
|
43
49
|
using Ractors: static analysis fans out across CPU cores on
|
|
44
|
-
Ractor workers, and
|
|
50
|
+
Ractor workers, and Audition passes its own audit. It runs on
|
|
51
|
+
itself on every commit (a lefthook pre-commit over the staged
|
|
52
|
+
files) and on every push (a full self-audit in CI).
|
|
45
53
|
- **Trained on Rails core.** Several checks and fix suggestions
|
|
46
54
|
come straight from studying the Rails ractorization effort
|
|
47
|
-
(
|
|
48
|
-
in-place mutation of registry constants,
|
|
49
|
-
`define_method`, copy-on-write rewrites. The
|
|
50
|
-
documented in
|
|
55
|
+
(some two hundred commits and pull requests): `Hash.new`
|
|
56
|
+
default procs, in-place mutation of registry constants,
|
|
57
|
+
closure-carrying `define_method`, copy-on-write rewrites. The
|
|
58
|
+
findings are documented in
|
|
51
59
|
[docs/rails_core_best_practices.md](docs/rails_core_best_practices.md).
|
|
52
60
|
- **Terminal-native output.** Colors, glyphs, and OSC 8 hyperlinks;
|
|
53
|
-
`path:line` is clickable in supporting terminals.
|
|
54
|
-
CI.
|
|
61
|
+
`path:line` is clickable in supporting terminals. Long scans
|
|
62
|
+
narrate their phases on stderr. JSON output for CI.
|
|
55
63
|
|
|
56
64
|
```console
|
|
57
65
|
$ audition worker.rb
|
|
58
|
-
*
|
|
66
|
+
* Audition 0.3.0 ruby 4.0.6 · script at .
|
|
59
67
|
|
|
60
68
|
worker.rb
|
|
61
|
-
x
|
|
62
|
-
access global
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
per-Ractor state use Ractor.current[:key].
|
|
72
|
-
x worker.rb:4 read of global variable $jobs
|
|
69
|
+
x worker.rb:1 write to global variable $jobs global-variables
|
|
70
|
+
why: Non-main Ractors cannot access global variables; this raises
|
|
71
|
+
Ractor::IsolationError the moment the line executes in a Ractor
|
|
72
|
+
(verified on Ruby 4.0).
|
|
73
|
+
fix: Pass the value into the Ractor explicitly (Ractor.new(value) {
|
|
74
|
+
|v| ... }) or over a Ractor::Port; for per-Ractor state use
|
|
75
|
+
Ractor.current[:key]; do one-time process setup on the main Ractor
|
|
76
|
+
before spawning.
|
|
77
|
+
x worker.rb:1 raises inside a Ractor: Ractor::IsolationError ...
|
|
78
|
+
x worker.rb:4 read of global variable $jobs global-variables
|
|
73
79
|
...
|
|
74
80
|
|
|
75
81
|
dynamic probes
|
|
@@ -85,16 +91,29 @@ And the whole-bundle view:
|
|
|
85
91
|
|
|
86
92
|
```console
|
|
87
93
|
$ audition Gemfile.lock --static-only
|
|
88
|
-
|
|
89
|
-
│
|
|
90
|
-
|
|
91
|
-
│
|
|
92
|
-
|
|
93
|
-
│
|
|
94
|
-
│
|
|
95
|
-
│
|
|
96
|
-
|
|
97
|
-
0
|
|
94
|
+
╭───────────────────────────────────────────────────────────────────────────────────────╮
|
|
95
|
+
│ Audition bundle sweep │
|
|
96
|
+
├──────────┬──────────┬─────────────┬────────┬────────────┬──────────┬─────────┬────────┤
|
|
97
|
+
│ gem │ version │ verdict │ errors │ dep errors │ warnings │ fixable │ status │
|
|
98
|
+
├──────────┼──────────┼─────────────┼────────┼────────────┼──────────┼─────────┼────────┤
|
|
99
|
+
│ rubocop │ 1.88.2 │ x not ready │ 194 │ - │ 203 │ 28 │ ok │
|
|
100
|
+
│ parser │ 3.3.12.0 │ x not ready │ 154 │ - │ 26 │ 26 │ ok │
|
|
101
|
+
│ rack │ 3.2.7 │ x not ready │ 31 │ - │ 61 │ 13 │ ok │
|
|
102
|
+
│ rake │ 13.4.2 │ x not ready │ 30 │ - │ 24 │ 5 │ ok │
|
|
103
|
+
│ prism │ 1.9.0 │ x not ready │ 16 │ - │ 88 │ 15 │ ok │
|
|
104
|
+
│ json │ 2.21.2 │ x not ready │ 7 │ - │ 5 │ - │ ok │
|
|
105
|
+
│ pastel │ 0.8.0 │ x not ready │ 2 │ - │ 1 │ - │ ok │
|
|
106
|
+
│ tty-link │ 0.2.0 │ x not ready │ 1 │ - │ 1 │ - │ ok │
|
|
107
|
+
╰──────────┴──────────┴─────────────┴────────┴────────────┴──────────┴─────────┴────────╯
|
|
108
|
+
|
|
109
|
+
x 0 of 8 gems ractor-ready · 8 not ready
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
Verdicts are colored by severity, clean counts are left blank, and
|
|
113
|
+
the sweep names each gem as it finishes:
|
|
114
|
+
|
|
115
|
+
```console
|
|
116
|
+
◆ Audition sweep rubocop 8/8 (100%) 5.8s
|
|
98
117
|
```
|
|
99
118
|
|
|
100
119
|
**Requires Ruby 4.0 or newer**, strictly: the tool targets the modern
|
|
@@ -112,6 +131,7 @@ proxying) and its verified semantics.
|
|
|
112
131
|
- [Installation](#installation)
|
|
113
132
|
- [Usage](#usage)
|
|
114
133
|
- [Adopting incrementally](#adopting-incrementally)
|
|
134
|
+
- [CI and git hooks](#ci-and-git-hooks)
|
|
115
135
|
- [What it catches](#what-it-catches)
|
|
116
136
|
- [Agent skill](#agent-skill)
|
|
117
137
|
- [Extending](#extending)
|
|
@@ -140,6 +160,7 @@ audition path/to/gem-checkout # a gem working copy (*.gemspec)
|
|
|
140
160
|
audition path/to/rack-app # a config.ru directory
|
|
141
161
|
audition path/to/rails-root # a Rails application
|
|
142
162
|
audition lib # any directory, static-only
|
|
163
|
+
audition a.rb b.rb c.rb # several files at once, statically
|
|
143
164
|
audition Gemfile.lock # sweep every gem in the bundle
|
|
144
165
|
audition path/to/app --deps # same, from the app root
|
|
145
166
|
```
|
|
@@ -154,16 +175,33 @@ Useful flags:
|
|
|
154
175
|
| `--fix-unsafe` | also apply semantics-affecting corrections |
|
|
155
176
|
| `--dry-run` | with a fix flag: preview edits, change nothing |
|
|
156
177
|
| `--format json` | machine-readable report for CI |
|
|
157
|
-
| `--format github` | GitHub Actions annotations
|
|
178
|
+
| `--format github` | GitHub Actions annotations + job summary |
|
|
158
179
|
| `--compare old.json` | delta vs a previous report: fixed/introduced |
|
|
159
180
|
| `--static-only` / `--dynamic-only` | pick one probe layer |
|
|
160
181
|
| `--fail-on warning` | stricter CI gate (default: error) |
|
|
182
|
+
| `--exit-zero` | report findings but never fail the build |
|
|
161
183
|
| `--capabilities` | table of what this Ruby allows in Ractors |
|
|
162
184
|
| `--timeout 60` | dynamic probe budget in seconds |
|
|
163
185
|
| `--plain` | no colors or hyperlinks (also via NO_COLOR, pipes) |
|
|
186
|
+
| `--progress` / `--no-progress` | force / suppress the stderr narration |
|
|
187
|
+
| `-j 4` / `--workers 4` | scan Ractors (default: cores, capped by `RUBY_MAX_CPU`) |
|
|
188
|
+
|
|
189
|
+
Long runs narrate themselves on stderr, so stdout stays pipeable:
|
|
190
|
+
a rewriting status line on a terminal, one line per phase off it.
|
|
191
|
+
|
|
192
|
+
```
|
|
193
|
+
◆ Audition checking 50/919 5% (0.2s, on 8 ractors)
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
A phase running in Ractors says how many. The narration turns on
|
|
197
|
+
by itself for a large tree or a bundle sweep and stays off for
|
|
198
|
+
`--format json` and `--format github`; the flags force either
|
|
199
|
+
way.
|
|
164
200
|
|
|
165
201
|
Exit codes: `0` clean, `1` findings at or above the `--fail-on`
|
|
166
202
|
threshold (or a failed dynamic probe), `2` usage error.
|
|
203
|
+
`--exit-zero` (alias for `--fail-on never`) always exits `0`
|
|
204
|
+
unless the invocation itself is broken.
|
|
167
205
|
|
|
168
206
|
## Adopting incrementally
|
|
169
207
|
|
|
@@ -197,11 +235,85 @@ timeout: 60
|
|
|
197
235
|
exclude:
|
|
198
236
|
- legacy/**
|
|
199
237
|
- db/schema.rb
|
|
238
|
+
test_dirs:
|
|
239
|
+
- qa
|
|
200
240
|
checks:
|
|
201
241
|
disable:
|
|
202
242
|
- at-exit
|
|
203
243
|
```
|
|
204
244
|
|
|
245
|
+
`test_dirs` names the directories that hold tests rather than
|
|
246
|
+
code a production boot loads; findings in them are tagged
|
|
247
|
+
`test` and rated as test code. It defaults to `test`, `spec`,
|
|
248
|
+
`features`, and replaces that list rather than adding to it—the
|
|
249
|
+
`_test.rb` and `_spec.rb` suffixes always count, whatever it
|
|
250
|
+
says.
|
|
251
|
+
|
|
252
|
+
## CI and git hooks
|
|
253
|
+
|
|
254
|
+
**GitHub Actions.** `--format github` turns findings into
|
|
255
|
+
workflow-command annotations that land right on the PR diff, and
|
|
256
|
+
appends a verdict-plus-counts markdown table to the job summary
|
|
257
|
+
page. A blocking gate:
|
|
258
|
+
|
|
259
|
+
```yaml
|
|
260
|
+
- uses: ruby/setup-ruby@v1
|
|
261
|
+
with:
|
|
262
|
+
ruby-version: "4.0"
|
|
263
|
+
- run: gem install audition
|
|
264
|
+
- run: audition --format github .
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
To surface findings without failing the build while you adopt
|
|
268
|
+
(the flag other linters call `--exit-zero` too, so it keeps its
|
|
269
|
+
name here):
|
|
270
|
+
|
|
271
|
+
```yaml
|
|
272
|
+
- run: audition --format github --exit-zero .
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
`--fail-on never` is the long form, and works from
|
|
276
|
+
`.audition.yml` as well; GitHub's own `continue-on-error: true`
|
|
277
|
+
on the step is the workflow-level equivalent.
|
|
278
|
+
|
|
279
|
+
**Git hooks.** Passing several `.rb` files audits exactly those
|
|
280
|
+
files statically, which is the shape hook managers hand over.
|
|
281
|
+
With [lefthook](https://github.com/evilmartians/lefthook):
|
|
282
|
+
|
|
283
|
+
```yaml
|
|
284
|
+
pre-commit:
|
|
285
|
+
commands:
|
|
286
|
+
audition:
|
|
287
|
+
glob: "*.rb"
|
|
288
|
+
run: audition --static-only --plain {staged_files}
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
With [pre-commit](https://pre-commit.com):
|
|
292
|
+
|
|
293
|
+
```yaml
|
|
294
|
+
- repo: local
|
|
295
|
+
hooks:
|
|
296
|
+
- id: audition
|
|
297
|
+
name: Audition
|
|
298
|
+
language: system
|
|
299
|
+
entry: audition --static-only --plain
|
|
300
|
+
types: [ruby]
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
Config, pragmas, and the baseline resolve against the working
|
|
304
|
+
directory, so a hook run from the repository root honors the
|
|
305
|
+
same `.audition.yml` as a full audit.
|
|
306
|
+
|
|
307
|
+
**This repository eats its own dog food.** Every commit runs
|
|
308
|
+
`audition --static-only` over the staged files through lefthook
|
|
309
|
+
(next to standardrb), and every push and pull request gets a
|
|
310
|
+
full self-audit in CI with annotations and a job summary,
|
|
311
|
+
non-blocking via `--exit-zero`
|
|
312
|
+
([audit.yml](.github/workflows/audit.yml)). Current state: own
|
|
313
|
+
code audits `ready`; the full dynamic probe reports the terminal
|
|
314
|
+
dev-dependencies as `blocked`, which is exactly the distinction
|
|
315
|
+
the verdict system exists to make.
|
|
316
|
+
|
|
205
317
|
## What it catches
|
|
206
318
|
|
|
207
319
|
Static, with file:line precision:
|
|
@@ -214,7 +326,7 @@ Static, with file:line precision:
|
|
|
214
326
|
`@cache ||= {}` and `return @x if defined?(@x)` memoizations.
|
|
215
327
|
- **Constants that are not deeply shareable**: bare mutable
|
|
216
328
|
literals, interpolated strings, the subtle shallow freeze
|
|
217
|
-
(`[[1], [2]].freeze` still raises;
|
|
329
|
+
(`[[1], [2]].freeze` still raises; Audition explains why), and
|
|
218
330
|
call results the magic comment never covers (`X.tr(":", "")`,
|
|
219
331
|
`Regexp.new`, `Regexp.union`, `format`), the shapes Rails fixed
|
|
220
332
|
last in its own ractorization. Honors `# frozen_string_literal:`
|
|
@@ -235,6 +347,25 @@ Static, with file:line precision:
|
|
|
235
347
|
`class_attribute` migration Rails itself made) and
|
|
236
348
|
`class_attribute` without copy-on-write writes,
|
|
237
349
|
`include Singleton`, `fork`, `ObjectSpace._id2ref`, ENV mutation.
|
|
350
|
+
- **Native extensions that never declare Ractor safety**: a byte
|
|
351
|
+
scan of every compiled `.bundle`/`.so` for the
|
|
352
|
+
`rb_ext_ractor_safe` import, which also covers precompiled
|
|
353
|
+
platform gems that ship no sources; an unbuilt checkout is
|
|
354
|
+
scanned at the source level instead (C, Rust, or Zig sources
|
|
355
|
+
beside their `extconf.rb`, `Cargo.toml` or `build.zig`).
|
|
356
|
+
A silent extension raises `Ractor::UnsafeError` on every call
|
|
357
|
+
from a non-main Ractor, so it rates a warning; a declared one
|
|
358
|
+
gets an info note, because the declaration is the maintainer's
|
|
359
|
+
assertion, not a proof.
|
|
360
|
+
- **The static pass's own blind spots**: where rubydex reports an
|
|
361
|
+
expression it could not resolve and the shape could hide what
|
|
362
|
+
the checks above look for, the hole is reported rather than
|
|
363
|
+
read as a clean line — a singleton opened on a runtime receiver
|
|
364
|
+
that writes class-level state, a superclass or `include`
|
|
365
|
+
argument computed at runtime, a constant assigned through an
|
|
366
|
+
unresolved path. A clean report for such a class covers only
|
|
367
|
+
what the class itself declares; the dynamic probe reaches the
|
|
368
|
+
rest.
|
|
238
369
|
|
|
239
370
|
Dynamic, on the live object graph:
|
|
240
371
|
|
|
@@ -244,6 +375,11 @@ Dynamic, on the live object graph:
|
|
|
244
375
|
`Ractor.shareable?`, and inspects every class and module for
|
|
245
376
|
class-level ivars and class variables, with
|
|
246
377
|
`const_source_location` attribution.
|
|
378
|
+
- Records every compiled extension the load pulled in,
|
|
379
|
+
dependencies included, and byte-scans each for the
|
|
380
|
+
`rb_ext_ractor_safe` import; silent ones are reported against
|
|
381
|
+
the dependency that ships them. Ruby's own extensions are left
|
|
382
|
+
to Ruby.
|
|
247
383
|
- Boots `config.ru` and serves one GET / entirely inside a Ractor,
|
|
248
384
|
the per-worker model of Ractor web servers; then hammers it from
|
|
249
385
|
4 Ractors x 25 requests to surface failures that only appear
|
|
@@ -254,7 +390,7 @@ Dynamic, on the live object graph:
|
|
|
254
390
|
## Agent skill
|
|
255
391
|
|
|
256
392
|
This repository ships a `ractor-readiness` skill that teaches
|
|
257
|
-
coding agents (Claude Code and friends) the full
|
|
393
|
+
coding agents (Claude Code and friends) the full Audition
|
|
258
394
|
workflow: audit, fix tiers, suite-parity verification, and
|
|
259
395
|
incremental adoption. It lives in
|
|
260
396
|
[skills/ractor-readiness/SKILL.md](skills/ractor-readiness/SKILL.md).
|
|
@@ -305,12 +441,12 @@ Audition::Static::Checks.register(NoSleep)
|
|
|
305
441
|
bundle install
|
|
306
442
|
bundle exec rake spec # RSpec suite
|
|
307
443
|
bundle exec rake standard # standardrb lint
|
|
308
|
-
lefthook install # pre-commit
|
|
444
|
+
lefthook install # pre-commit standardrb + audition
|
|
309
445
|
bundle exec exe/audition --capabilities
|
|
310
446
|
```
|
|
311
447
|
|
|
312
448
|
Static scanning is Ractor-parallel on large targets (one worker
|
|
313
|
-
per core, minus one for the main Ractor);
|
|
449
|
+
per core, minus one for the main Ractor); Audition's own `lib/`
|
|
314
450
|
passes `audition lib` clean.
|
|
315
451
|
|
|
316
452
|
The design notes in `docs/design.md` include the empirically
|
|
@@ -321,7 +457,7 @@ calibrated against.
|
|
|
321
457
|
|
|
322
458
|
The whole-program checks stand on
|
|
323
459
|
[rubydex](https://github.com/Shopify/rubydex), Shopify's
|
|
324
|
-
high-performance static analysis suite for Ruby:
|
|
460
|
+
high-performance static analysis suite for Ruby: Audition feeds
|
|
325
461
|
every file into its graph and reads state ownership back out.
|
|
326
462
|
Thanks to its authors, in particular the top five contributors:
|
|
327
463
|
[Alexandre Terrasa](https://github.com/Morriar),
|
|
@@ -31,10 +31,10 @@ module Audition
|
|
|
31
31
|
|
|
32
32
|
# Audits every locked gem and ranks the results, worst first.
|
|
33
33
|
#
|
|
34
|
-
# @param progress [
|
|
35
|
-
#
|
|
34
|
+
# @param progress [Progress] named per gem as each finishes,
|
|
35
|
+
# which is the only unit a sweep has
|
|
36
36
|
# @return [Array<Row>]
|
|
37
|
-
def rows(progress:
|
|
37
|
+
def rows(progress: Progress::SILENT)
|
|
38
38
|
gems = locked_gems
|
|
39
39
|
queue = Thread::Queue.new
|
|
40
40
|
gems.each { |g| queue << g }
|
|
@@ -42,17 +42,19 @@ module Audition
|
|
|
42
42
|
|
|
43
43
|
collected = []
|
|
44
44
|
mutex = Thread::Mutex.new
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
45
|
+
progress.phase("sweep", total: gems.size, unit: "gems") do
|
|
46
|
+
@concurrency.times.map do
|
|
47
|
+
Thread.new do
|
|
48
|
+
while (name, version = queue.pop)
|
|
49
|
+
row = audit_gem(name, version)
|
|
50
|
+
mutex.synchronize do
|
|
51
|
+
collected << row
|
|
52
|
+
progress.item(row.name)
|
|
53
|
+
end
|
|
52
54
|
end
|
|
53
55
|
end
|
|
54
|
-
end
|
|
55
|
-
end
|
|
56
|
+
end.each(&:join)
|
|
57
|
+
end
|
|
56
58
|
|
|
57
59
|
collected.sort_by do |row|
|
|
58
60
|
[VERDICT_ORDER.fetch(row.verdict), -row.errors, row.name]
|
|
@@ -115,7 +117,10 @@ module Audition
|
|
|
115
117
|
ruby_files: spec.require_paths.flat_map do |rp|
|
|
116
118
|
ruby_files_under(File.join(root, rp))
|
|
117
119
|
end,
|
|
118
|
-
entry: {mode: :require, feature: spec.name,
|
|
120
|
+
entry: {mode: :require, feature: spec.name,
|
|
121
|
+
load_paths: spec.full_require_paths, root: root},
|
|
122
|
+
compiled_files: Target.compiled_for(spec),
|
|
123
|
+
stub_files: Target.stubs(root)
|
|
119
124
|
)
|
|
120
125
|
end
|
|
121
126
|
|
|
@@ -142,16 +147,28 @@ module Audition
|
|
|
142
147
|
files = target.ruby_files.reject do |file|
|
|
143
148
|
config.excluded?(file.delete_prefix("#{target.root}/"))
|
|
144
149
|
end
|
|
150
|
+
compiled = target.compiled_files.reject do |file|
|
|
151
|
+
config.excluded?(file.delete_prefix("#{target.root}/"))
|
|
152
|
+
end
|
|
145
153
|
per_file = Static::Analyzer.new
|
|
146
154
|
.analyze_paths(files, workers: 1)
|
|
147
|
-
|
|
155
|
+
gem_calls = Static::GemCalls
|
|
156
|
+
.new(root: target.root, stubs: target.stub_files)
|
|
157
|
+
.analyze_paths(files)
|
|
158
|
+
per_file + gem_calls + Static::GraphAudit.new
|
|
159
|
+
.analyze_paths(files, constant_findings: per_file + gem_calls,
|
|
160
|
+
workers: 1) +
|
|
161
|
+
Static::NativeExtensions.new.analyze(target,
|
|
162
|
+
compiled_files: compiled)
|
|
148
163
|
end
|
|
149
164
|
|
|
150
165
|
def dynamic_results(target)
|
|
151
166
|
return [] if @static_only || target.entry.nil?
|
|
152
167
|
|
|
153
168
|
prober = Dynamic::Prober.new(timeout: @timeout)
|
|
154
|
-
[prober.probe(
|
|
169
|
+
[prober.probe(
|
|
170
|
+
target.entry.merge(compiled_files: target.compiled_files)
|
|
171
|
+
)]
|
|
155
172
|
end
|
|
156
173
|
end
|
|
157
174
|
end
|