mirror of
https://github.com/projectdiscovery/nuclei.git
synced 2026-01-31 15:53:10 +08:00
`MergeMaps` accounts for 11.41% of allocs (13.8 GB) in clusterbomb mode. With 1,305 combinations per target, this function is called millions of times in the hot path. RCA: * Request generator calls `MergeMaps` with single arg on every payload combination, incurring variadic overhead. * Build request merges same maps multiple times per request. * `BuildPayloadFromOptions` recomputes static CLI options on every call. * Variables calls `MergeMaps` $$2×N$$ times per variable evaluation (once in loop, once in `evaluateVariableValue`) Changes: Core optimizations in maps.go: * Pre-size merged map to avoid rehashing (30-40% reduction) * Add `CopyMap` for efficient single-map copy without variadic overhead. * Add `MergeMapsInto` for in-place mutation when caller owns destination. Hot path fixes: * Replace `MergeMaps(r.currentPayloads)` with `CopyMap(r.currentPayloads)` to eliminates allocation on every combination iteration. * Pre-allocate combined map once, extend in-place during `ForEach` loop instead of creating new map per variable (eliminates $$2×N$$ allocations per request). Caching with concurrency safety: * Cache `BuildPayloadFromOptions` computation in `sync.Map` keyed by `types.Options` ptr, but return copy to prevent concurrent modification. * Cost: shallow copy of ~10-20 entries vs. full merge of vars + env (85-90% savings in typical case) * Clear cache in `closeInternal()` to prevent memory leaks when SDK instances are created or destroyed. Estimated impact: 40-60% reduction in `MergeMaps` allocations (5.5-8.3 GB savings from original 13.8 GB). Safe for concurrent execution and SDK usage with multiple instances. Signed-off-by: Dwi Siswanto <git@dw1.io>
95 lines
2.1 KiB
Go
95 lines
2.1 KiB
Go
package generators
|
|
|
|
import (
|
|
maps0 "maps"
|
|
"reflect"
|
|
)
|
|
|
|
// MergeMapsMany merges many maps into a new map
|
|
func MergeMapsMany(maps ...interface{}) map[string][]string {
|
|
m := make(map[string][]string)
|
|
for _, gotMap := range maps {
|
|
val := reflect.ValueOf(gotMap)
|
|
if val.Kind() != reflect.Map {
|
|
continue
|
|
}
|
|
appendToSlice := func(key, value string) {
|
|
if values, ok := m[key]; !ok {
|
|
m[key] = []string{value}
|
|
} else {
|
|
m[key] = append(values, value)
|
|
}
|
|
}
|
|
for _, e := range val.MapKeys() {
|
|
v := val.MapIndex(e)
|
|
switch v.Kind() {
|
|
case reflect.Slice, reflect.Array:
|
|
for i := 0; i < v.Len(); i++ {
|
|
appendToSlice(e.String(), v.Index(i).String())
|
|
}
|
|
case reflect.String:
|
|
appendToSlice(e.String(), v.String())
|
|
case reflect.Interface:
|
|
switch data := v.Interface().(type) {
|
|
case string:
|
|
appendToSlice(e.String(), data)
|
|
case []string:
|
|
for _, value := range data {
|
|
appendToSlice(e.String(), value)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return m
|
|
}
|
|
|
|
// MergeMaps merges multiple maps into a new map.
|
|
//
|
|
// Use [CopyMap] if you need to copy a single map.
|
|
// Use [MergeMapsInto] to merge into an existing map.
|
|
func MergeMaps(maps ...map[string]interface{}) map[string]interface{} {
|
|
mapsLen := 0
|
|
for _, m := range maps {
|
|
mapsLen += len(m)
|
|
}
|
|
|
|
merged := make(map[string]interface{}, mapsLen)
|
|
for _, m := range maps {
|
|
maps0.Copy(merged, m)
|
|
}
|
|
|
|
return merged
|
|
}
|
|
|
|
// CopyMap creates a shallow copy of a single map.
|
|
func CopyMap(m map[string]interface{}) map[string]interface{} {
|
|
if m == nil {
|
|
return nil
|
|
}
|
|
|
|
result := make(map[string]interface{}, len(m))
|
|
maps0.Copy(result, m)
|
|
|
|
return result
|
|
}
|
|
|
|
// MergeMapsInto copies all entries from src maps into dst (mutating dst).
|
|
//
|
|
// Use when dst is a fresh map the caller owns and wants to avoid allocation.
|
|
func MergeMapsInto(dst map[string]interface{}, srcs ...map[string]interface{}) {
|
|
for _, src := range srcs {
|
|
maps0.Copy(dst, src)
|
|
}
|
|
}
|
|
|
|
// ExpandMapValues converts values from flat string to string slice
|
|
func ExpandMapValues(m map[string]string) map[string][]string {
|
|
m1 := make(map[string][]string, len(m))
|
|
for k, v := range m {
|
|
m1[k] = []string{v}
|
|
}
|
|
|
|
return m1
|
|
}
|