Skip to content

refactor: replace Split in loops with more efficient SplitSeq - #7278

Merged
Mzack9999 merged 10 commits into
projectdiscovery:devfrom
stringsbuilder:dev
Jul 22, 2026
Merged

refactor: replace Split in loops with more efficient SplitSeq#7278
Mzack9999 merged 10 commits into
projectdiscovery:devfrom
stringsbuilder:dev

Conversation

@stringsbuilder

@stringsbuilder stringsbuilder commented Mar 20, 2026

Copy link
Copy Markdown
Contributor

Proposed changes

Closes #7561

strings.SplitSeq (introduced in Go 1.24) returns a lazy sequence, allowing iteration over tokens one by one without creating an intermediate slice.

It significantly reduces memory allocations and can improve performance for long strings.

More info: golang/go#61901

Proof

Checklist

  • Pull request is created against the dev branch
  • All checks passed (lint, unit/integration/regression tests etc.) with my changes
  • I have added tests that prove my fix is effective or that my feature works
  • I have added necessary documentation (if appropriate)

Summary by CodeRabbit

  • Refactor
    • Reduced allocations by switching comma-/newline-delimited parsing to efficient sequence iteration across request/port parsing, template utilities, checksum/token processing, and raw HTTP fuzz/YAML generation.
    • Streamlined helper-file reading to process input line-by-line while keeping the same trimming/filtering behavior.
  • Bug Fixes
    • Preserved existing behavior for debug/argument, port, and checksum/token parsing while adjusting iteration mechanics.
  • Tests
    • Updated integration test output parsing to match the improved iteration approach.
    • Adjusted SDK tests to disable update checking and use clearer NoError assertions, plus refined goroutine-leak ignores.

Signed-off-by: stringsbuilder <stringsbuilder@outlook.com>
@auto-assign
auto-assign Bot requested a review from dogancanbakir March 20, 2026 11:59
@coderabbitai

coderabbitai Bot commented Mar 20, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

This PR replaces eager string splitting with sequence-based iteration across parsing, protocol, template, and integration-test paths. ReadDataFromFile now reads helper files incrementally with buffered line handling, while SDK tests improve update-check isolation and error assertions.

Changes

String Parsing Refactoring

Layer / File(s) Summary
Streaming helper-file reads
pkg/tmplexec/flow/flow_executor.go
ReadDataFromFile uses buffered line reads, handles EOF and partial final lines, and filters trimmed empty lines.
Production sequence-based parsing
pkg/catalog/config/*, pkg/installer/template.go, pkg/protocols/*, internal/runner/*, pkg/templates/fuzz_harness.go, pkg/js/devtools/tsgen/parser.go
Parsing loops use SplitSeq or FieldsSeq; debug key/value parsing uses strings.Cut while downstream behavior remains unchanged.
Integration output iteration
internal/tests/integration/http_test.go
Five Execute methods use SplitSeq for output lines while preserving GET extraction and validation logic.
SDK test setup and assertions
lib/tests/sdk_test.go
SDK tests disable update checks, replace nil assertions with require.NoError, close initialized engines, and expand goroutine-leak ignore patterns.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Possibly related PRs

Suggested reviewers: dogancanbakir

Poem

🐰 Splits now stream and gently flow,
Lines are read as they go,
Ports and paths keep their tune,
Tests stay tidy beneath the moon.

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning sdk_test.go adds update-check and goleak test hardening that is unrelated to the SplitSeq/flow scope. Move the SDK test hardening into a separate PR or remove it from this scope.
Docstring Coverage ⚠️ Warning Docstring coverage is 71.43% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main refactor: replacing looped Split calls with SplitSeq.
Linked Issues check ✅ Passed The changes mostly match #7561: one-pass splits use SplitSeq/FieldsSeq and the flow helper is streamed line by line.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
pkg/protocols/network/network.go (1)

202-213: ⚠️ Potential issue | 🟡 Minor

Trim whitespace before parsing each port token.

Line 206 will reject a common input like port: "80, 443" because the second token still includes leading whitespace.

✂️ Suggested fix
-		for port := range strings.SplitSeq(request.Port, ",") {
+		for rawPort := range strings.SplitSeq(request.Port, ",") {
+			port := strings.TrimSpace(rawPort)
 			if port == "" {
 				continue
 			}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pkg/protocols/network/network.go` around lines 202 - 213, When iterating
tokens from strings.SplitSeq(request.Port, ",") trim whitespace from each token
before using it: use a local variable (e.g., tok := strings.TrimSpace(port)),
skip if tok == "", parse tok with strconv.Atoi, validate its range, and append
the trimmed token (tok) to request.ports (not the original port with
whitespace). Update the error messages to include tok where appropriate so
parsing failures reflect the trimmed value; keep references to request.Port,
strings.SplitSeq, strconv.Atoi, and request.ports.
🧹 Nitpick comments (2)
pkg/tmplexec/flow/flow_executor.go (1)

302-311: This still materializes the entire helper file before tokenization.

Line 302's io.ReadAll(reader) dominates the allocation profile here, and the substrings appended in Lines 306-310 keep that full backing string alive. If this path is part of the perf goal, scan the reader directly instead.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pkg/tmplexec/flow/flow_executor.go` around lines 302 - 311, The code
currently calls io.ReadAll(reader) and then iterates over
strings.SplitSeq(string(bin), "\n"), which materializes the entire reader into
memory and keeps the large backing string alive; replace that pattern by
scanning the reader directly with a bufio.Scanner (or
bufio.Reader.ReadString/ReadBytes loop) to read lines one-by-one from reader,
trim each scanned line (strings.TrimSpace), and append non-empty lines to
values, and if necessary configure Scanner.Buffer to handle long lines; update
the logic that references reader, values, and strings.SplitSeq to use the
scanner instead.
pkg/installer/template.go (1)

568-575: There's still a per-record split allocation in this loop.

Line 570 only needs two fields, but the full split still creates a slice for every checksum entry. A two-part split would align better with the allocation-reduction goal of this PR.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pkg/installer/template.go` around lines 568 - 575, The per-record allocation
comes from using strings.Split on each checksum line (variable v) and assigning
to tmparr; replace that with a two-field split (e.g., strings.SplitN(v, ",", 2)
or better strings.Cut(v, ",")) to avoid allocating a full slice for every entry,
then validate the second field exists before assigning into
allChecksums[tmparr[0]] = tmparr[1] (or using the two return values from
strings.Cut) while keeping the surrounding loop and trimming logic the same.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@pkg/protocols/network/network.go`:
- Around line 202-213: When iterating tokens from strings.SplitSeq(request.Port,
",") trim whitespace from each token before using it: use a local variable
(e.g., tok := strings.TrimSpace(port)), skip if tok == "", parse tok with
strconv.Atoi, validate its range, and append the trimmed token (tok) to
request.ports (not the original port with whitespace). Update the error messages
to include tok where appropriate so parsing failures reflect the trimmed value;
keep references to request.Port, strings.SplitSeq, strconv.Atoi, and
request.ports.

---

Nitpick comments:
In `@pkg/installer/template.go`:
- Around line 568-575: The per-record allocation comes from using strings.Split
on each checksum line (variable v) and assigning to tmparr; replace that with a
two-field split (e.g., strings.SplitN(v, ",", 2) or better strings.Cut(v, ","))
to avoid allocating a full slice for every entry, then validate the second field
exists before assigning into allChecksums[tmparr[0]] = tmparr[1] (or using the
two return values from strings.Cut) while keeping the surrounding loop and
trimming logic the same.

In `@pkg/tmplexec/flow/flow_executor.go`:
- Around line 302-311: The code currently calls io.ReadAll(reader) and then
iterates over strings.SplitSeq(string(bin), "\n"), which materializes the entire
reader into memory and keeps the large backing string alive; replace that
pattern by scanning the reader directly with a bufio.Scanner (or
bufio.Reader.ReadString/ReadBytes loop) to read lines one-by-one from reader,
trim each scanned line (strings.TrimSpace), and append non-empty lines to
values, and if necessary configure Scanner.Buffer to handle long lines; update
the logic that references reader, values, and strings.SplitSeq to use the
scanner instead.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: bb4462ab-2d3f-4a47-917f-e710b053336f

📥 Commits

Reviewing files that changed from the base of the PR and between 0ae6d90 and 90cd656.

📒 Files selected for processing (10)
  • cmd/integration-test/http.go
  • internal/runner/options_test.go
  • pkg/catalog/config/nucleiconfig.go
  • pkg/catalog/config/template.go
  • pkg/installer/template.go
  • pkg/js/devtools/tsgen/parser.go
  • pkg/protocols/javascript/js.go
  • pkg/protocols/network/network.go
  • pkg/testutils/integration.go
  • pkg/tmplexec/flow/flow_executor.go

@neo-by-projectdiscovery-dev

neo-by-projectdiscovery-dev Bot commented Mar 20, 2026

Copy link
Copy Markdown

Neo - PR Security Review

No security issues found

Highlights

  • Refactors strings.Split/Fields to lazy iterator-based strings.SplitSeq/FieldsSeq (Go 1.23)
  • Changes span 10 files: port parsing, path validation, checksum parsing, test utilities, and config parsing
  • All security validations (port validation, path traversal checks) remain functionally identical
Hardening Notes
  • The lazy evaluation in SplitSeq defers memory allocation but does not change iteration logic
  • Port validation in network.go and js.go correctly validates each port with the same checks as before
  • Path traversal prevention in template.go still checks each path component against excluded directories
  • Checksum parsing in installer.go processes trusted local files with the same validation
  • The refactoring reduces memory allocations, which is beneficial for DoS prevention

Comment @pdneo help for available commands. · Open in Neo

@themavik themavik left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SplitSeq/FieldsSeq churn is straightforward. nit: a few call sites still use strings.Split for small inner splits (e.g. installer checksum line) — not wrong, just inconsistent if you are chasing allocs everywhere.

@stringsbuilder

Copy link
Copy Markdown
Contributor Author

@Mzack9999 @dogancanbakir Hi, Could you please review this PR at your convenience? Thank you very much.

@Mzack9999

Copy link
Copy Markdown
Member

Holding review until #7419 is merged. The Memoize Functions workflow on contributor forks is auto-pushing a broken regeneration onto any branch named dev (template sentinel for context.Context is stale, ctx ends up in the memoization hash). #7419 fixes the template and scopes the workflow to the upstream repo. Once it lands we'll sync this branch and resume.

@Mzack9999
Mzack9999 self-requested a review July 22, 2026 12:26

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
internal/runner/preflight_portscan.go (1)

264-269: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Make per-port counter initialization atomic.

Concurrent first hits can both observe nil, create separate counters, and overwrite one another in perPortOpen, causing the open-port distribution summary to undercount. Preinitialize counters for portsToScan or guard the Get/Create/Set sequence with a mutex.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/runner/preflight_portscan.go` around lines 264 - 269, Make the
perPortOpen counter setup in the port-scan aggregation path atomic so concurrent
first hits cannot overwrite each other. Prefer preinitializing one atomic
counter for every port in portsToScan before processing results, or protect the
existing Get/Create/Set sequence with a mutex; preserve the subsequent
counter.Add(1) behavior.
pkg/installer/template.go (1)

586-593: 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Parse checksum entries at the final comma. writeChecksumFileInDir stores raw paths with no escaping, so strings.Split(v, ",") will discard any entry whose path contains a comma. Use the last comma as the separator instead.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pkg/installer/template.go` around lines 586 - 593, Update the checksum
parsing loop in writeChecksumFileInDir to split each entry at the final comma
rather than using strings.Split, preserving commas within the path while
separating it from the checksum. Continue skipping entries that lack a valid
separator and store the parsed path and checksum in allChecksums.
🧹 Nitpick comments (1)
internal/runner/preflight_portscan.go (1)

200-206: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Remove or consume the unused allIPs aggregation.

The code builds and sorts every resolved IP, but allIPs is never used by the subsequent per-target scan. For large inputs this adds unnecessary O(n) memory and O(n log n) sorting overhead.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/runner/preflight_portscan.go` around lines 200 - 206, Remove the
unused allIPsMap/allIPs aggregation and its sort.Strings call from the preflight
scan setup. Keep allIPsSet available for the subsequent per-target scan, and
remove any imports that become unused.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@internal/runner/preflight_portscan.go`:
- Around line 264-269: Make the perPortOpen counter setup in the port-scan
aggregation path atomic so concurrent first hits cannot overwrite each other.
Prefer preinitializing one atomic counter for every port in portsToScan before
processing results, or protect the existing Get/Create/Set sequence with a
mutex; preserve the subsequent counter.Add(1) behavior.

In `@pkg/installer/template.go`:
- Around line 586-593: Update the checksum parsing loop in
writeChecksumFileInDir to split each entry at the final comma rather than using
strings.Split, preserving commas within the path while separating it from the
checksum. Continue skipping entries that lack a valid separator and store the
parsed path and checksum in allChecksums.

---

Nitpick comments:
In `@internal/runner/preflight_portscan.go`:
- Around line 200-206: Remove the unused allIPsMap/allIPs aggregation and its
sort.Strings call from the preflight scan setup. Keep allIPsSet available for
the subsequent per-target scan, and remove any imports that become unused.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 36295919-c145-4487-b004-473a643d2b2c

📥 Commits

Reviewing files that changed from the base of the PR and between 0f2565a and 1a17b4e.

📒 Files selected for processing (9)
  • internal/runner/preflight_portscan.go
  • internal/tests/integration/http_test.go
  • pkg/catalog/config/nucleiconfig.go
  • pkg/catalog/config/template.go
  • pkg/installer/template.go
  • pkg/protocols/javascript/js.go
  • pkg/protocols/network/network.go
  • pkg/templates/fuzz_harness.go
  • pkg/tmplexec/flow/flow_executor.go
🚧 Files skipped from review as they are similar to previous changes (4)
  • pkg/protocols/javascript/js.go
  • pkg/catalog/config/template.go
  • pkg/catalog/config/nucleiconfig.go
  • internal/tests/integration/http_test.go

Avoid shared templates-dir update races across parallel go test packages and ignore known leveldb/ratelimit/memguardian goroutines in goleak.
Keep SplitSeq inputs in locals so iterator substrings cannot outlive a temporary string expression.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
lib/tests/sdk_test.go (1)

23-30: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Narrow the package-wide goleak suppressions.

IgnoreAnyContainingPkg hides every goroutine stack containing the expirable LRU, goleveldb, or ratelimit package paths—not only the documented long-lived workers. Prefer exact goroutine-function ignores, or verify that this broad suppression is intentional and covered separately.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@lib/tests/sdk_test.go` around lines 23 - 30, Narrow the goleak suppressions
in the SDK leak test by replacing the package-wide IgnoreAnyContainingPkg
entries for expirable LRU, goleveldb, and ratelimit with exact
goroutine-function ignores for only their documented long-lived workers. Keep
the existing memguardian function-specific suppression, and verify each
replacement targets the intended worker rather than unrelated goroutines from
the same package.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@lib/tests/sdk_test.go`:
- Around line 23-30: Narrow the goleak suppressions in the SDK leak test by
replacing the package-wide IgnoreAnyContainingPkg entries for expirable LRU,
goleveldb, and ratelimit with exact goroutine-function ignores for only their
documented long-lived workers. Keep the existing memguardian function-specific
suppression, and verify each replacement targets the intended worker rather than
unrelated goroutines from the same package.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 0bbcac1f-5fd3-42bf-bae2-88e6cca2db2b

📥 Commits

Reviewing files that changed from the base of the PR and between 375659a and cef1c54.

📒 Files selected for processing (3)
  • lib/tests/sdk_test.go
  • pkg/installer/template.go
  • pkg/templates/fuzz_harness.go
🚧 Files skipped from review as they are similar to previous changes (2)
  • pkg/installer/template.go
  • pkg/templates/fuzz_harness.go

Keep goleak ignores, drop DisableUpdateCheck so CI can install templates, and serialize UpdateIfOutdated across processes.
@Mzack9999
Mzack9999 merged commit ce470c3 into projectdiscovery:dev Jul 22, 2026
29 of 30 checks passed
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.

Use strings.SplitSeq/FieldsSeq for one-pass string splits

3 participants