github-vet / github-vet/rangeloop-pointer-findings
couchbaselabs/sequoia: lib/test.go; 339 LoC
- Dominant language
- No language data
- Stars
- 0
- Forks
- 0
- PR merge metrics
- PR metrics pending
Description
Found a possible issue in [couchbaselabs/sequoia](https://www.github.com/couchbaselabs/sequoia) at [lib/test.go](https://github.com/couchbaselabs/sequoia/blob/4fe65c69b3522a82e0795338308d4683f76c73dc/lib/test.go#L293-L631)
Below is the message reported by the analyzer for this snippet of code. Beware that the analyzer only reports the first issue it finds, so please do not limit your consideration to the contents of the below message.
> function call which takes a reference to action at line 618 may start a goroutine
[Click here to see the code in its original context.](https://github.com/couchbaselabs/sequoia/blob/4fe65c69b3522a82e0795338308d4683f76c73dc/lib/test.go#L293-L631)
Click here to show the 339 line(s) of Go which triggered the analyzer.
```go
for _, action := range actions {
if action.ForEach != "" {
// resolve foreach template (must result in an iterable)
// create actions with '.' as the output of the range
rangeActions := t.ResolveSingleRangeActions(scope, action)
t.runActions(scope, loop, rangeActions)
continue
}
if action.Client.Op != "" {
key := action.Client.Container
// is a client op
switch action.Client.Op {
case "kill":
if id, ok := scope.GetVarsKV(key); ok {
t.Cm.KillContainer(id)
colorsay("kill" + key)
} else {
ecolorsay("no such container alias " + key)
}
case "rm":
if id, ok := scope.GetVarsKV(key); ok {
t.Cm.RemoveContainer(id)
colorsay("remove " + key)
} else {
ecolorsay("no such container alias " + key)
}
case "cp":
// allow parsing of topath
action.Client.ToPath = ParseTemplate(&scope, action.Client.ToPath)
if id, ok := scope.GetVarsKV(key); ok {
t.Cm.CopyFromContainer(id,
PathToFilename(action.Client.ToPath),
action.Client.FromPath,
PathToDir(action.Client.ToPath))
msg := fmt.Sprintf("copying files from %s:%s to %s",
id[:6],
action.Client.FromPath,
action.Client.ToPath)
colorsay(msg)
} else {
ecolorsay("no such container alias " + key)
}
case "exec":
// enter into container
if id, ok := scope.GetVarsKV(key); ok {
colorsay("docker exec -it " + id + " bash")
subProcess := exec.Command("docker", "-H", *t.Flags.Client, "exec", "-it", id, "bash")
stdin, err := subProcess.StdinPipe()
logerr(err)
defer stdin.Close() // the doc says subProcess.Wait will close it, but I'm not sure, so I kept this line
subProcess.Stdin = os.Stdin
subProcess.Stdout = os.Stdout
subProcess.Stderr = os.Stderr
if err := subProcess.Start(); err != nil {
emsg := fmt.Sprintf("%s [%s] %s",
"failed to exec into container ",
id,
err)
ecolorsay(emsg)
} else {
// wait for process to quit and cleanup
subProcess.Wait()
*t.Flags.SoftCleanup = false // purge debug containers
t.Cleanup(scope)
return
}
} else {
ecolorsay("no such container alias " + key)
}
}
continue
}
if action.SectionStart != "" ||
action.SectionEnd != "" ||
action.SectionTag != "" {
continue
}
if action.Scope != "" {
// transform cluster scope
newSpec := NewScopeSpec(action.Scope)
scope.Teardown()
if scope.Provider.GetType() == "docker" {
for i, s := range newSpec.Servers {
if i <= len(scope.Spec.Servers) { // same num of clusters
s.Count -= scope.Spec.Servers[i].Count
s.InitNodes -= scope.Spec.Servers[i].InitNodes
if s.Count < 0 {
s.Count = 0
}
if s.InitNodes < 0 {
s.InitNodes = 0
}
offset := newSpec.Servers[i].Count - s.Count
newSpec.Servers[i].CountOffset = offset
newSpec.Servers[i].Count = s.Count
newSpec.Servers[i].InitNodes = s.InitNodes
}
}
}
scope.Spec = newSpec
scope.Provider.ProvideCouchbaseServers(t.Flags.ProviderConfig, scope.Spec.Servers)
scope.SetupServer()
}
if action.Test != "" {
// referencing external test
testActions := ActionsFromFile(action.Test)
// filter by section if provided
var sectionName string
if action.SectionSkip != "" {
sectionName = action.SectionSkip
} else {
sectionName = action.Section
}
excludedActions := []ActionSpec{}
if sectionName != "" {
t.Actions = []ActionSpec{}
isWithinSection := false
for _, action := range testActions {
if action.SectionStart == sectionName {
isWithinSection = true
}
if action.SectionEnd == sectionName {
isWithinSection = false
}
// add action if it's within a section or matches tag
if isWithinSection || (action.SectionTag == sectionName) {
t.Actions = append(t.Actions, action)
} else if action.Include != "" {
// add any includes needed for test actions
t.Actions = append(t.Actions, action)
} else {
excludedActions = append(excludedActions, action)
}
}
} else {
t.Actions = testActions
}
if action.SectionSkip != "" {
// skipped actions
t.Actions = excludedActions
}
// save test options
setup := t.Flags.SkipSetup
teardown := t.Flags.SkipTeardown
cleanup := t.Flags.SkipCleanup
duration := t.Flags.Duration
repeat := t.Flags.Repeat
ok := true
zero := 0
t.Flags.SkipSetup = &ok
t.Flags.SkipTeardown = &ok
t.Flags.SkipCleanup = &ok
t.Flags.Duration = &zero
t.Flags.Repeat = &action.Repeat
// run test
t.Run(scope)
// restore options
t.Flags.SkipSetup = setup
t.Flags.SkipTeardown = teardown
t.Flags.SkipCleanup = cleanup
t.Flags.Duration = duration
t.Flags.Repeat = repeat
continue
}
if action.Include != "" {
for _, includeFile := range strings.Split(action.Include, ",") {
includeFile = strings.TrimSpace(includeFile)
// include template file
var spec []TemplateSpec
ReadYamlFile(includeFile, &spec)
t.CacheIncludedTemplate(scope, spec)
}
continue
}
// check if action provides args to a template
if action.Template != "" || action.Args != "" {
if action.Template == "" {
// use last template
if lastAction.Template != "" {
action.Template = lastAction.Template
} else {
ecolorsay("ERROR: cannot provide args without template: " + action.Args)
}
}
// run template actions
if templateActions, ok := t.Templates[action.Template]; ok {
templateActions = t.ResolveTemplateActions(scope, action)
t.runActions(scope, loop, templateActions)
} else {
ecolorsay("WARNING template not found: " + action.Template)
}
lastAction = action
continue
}
if action.Image == "" {
if lastAction.Image != "" {
// reuse last action image
action.Image = lastAction.Image
}
if action.Template == "" && lastAction.Template != "" {
// reuse last action template
action.Template = lastAction.Template
}
// reuse last action requires
if action.Requires == "" {
action.Requires = lastAction.Requires
}
// reuse last duration
if action.Duration == "" {
action.Duration = lastAction.Duration
}
// reuse last concurrency
if action.Concurrency == "" {
action.Concurrency = lastAction.Concurrency
}
}
// check action requirements
if action.Requires != "" {
ok := ParseTemplate(&scope, action.Requires)
pass, err := strconv.ParseBool(ok)
logerr(err)
if pass == false {
lastAction = action
continue
}
}
if action.CondWait != "" {
ok := ParseTemplate(&scope, action.CondWait)
ok = strings.TrimSpace(ok)
if wait, err := strconv.ParseBool(ok); err == nil {
action.Wait = wait
}
}
// resolve command
command := scope.CompileCommand(action.Command)
// resolve duration and concurrency
var taskDuration time.Duration = 0
var taskConcurrency = 0
var err error
if action.Duration != "" {
// parse template if units not found
if strings.Index(action.Duration, "ns") == -1 {
action.Duration = fmt.Sprintf("%s%s", ParseTemplate(&scope, action.Duration), "ns")
}
taskDuration, err = time.ParseDuration(action.Duration)
logerr(err)
}
if action.Concurrency != "" {
action.Concurrency = ParseTemplate(&scope, action.Concurrency)
taskConcurrency, err = strconv.Atoi(action.Concurrency)
logerr(err)
}
if action.Describe == "" { // use command as describe
action.Describe = fmt.Sprintf("start %s: %s", action.Image, strings.Join(command, " "))
}
// If volumes are supplies, the container will mount them
// when launching. The format of the volume string should be:
// "/folder1://folder1,/file1://file2"
// folder1 and file1 must be in the samd
volumes := []string{}
if action.Volumes != "" {
volumes = BuildVolumes(action.Volumes)
}
// compile task
task := ContainerTask{
Name: *t.Flags.ContainerName,
Describe: action.Describe,
Volumes: volumes,
Image: action.Image,
Command: command,
Async: !action.Wait,
Duration: taskDuration,
Concurrency: taskConcurrency,
LogLevel: *t.Flags.LogLevel,
LogDir: *t.Flags.LogDir,
CIDs: []string{},
}
if scope.Provider.GetType() == "docker" {
task.LinksTo = scope.Provider.(*DockerProvider).GetLinkPairs()
} else if scope.Provider.GetType() == "swarm" {
task.LinksTo = scope.Provider.(*SwarmProvider).GetLinkPairs()
}
if action.Entrypoint != "" {
task.Entrypoint = []string{action.Entrypoint}
}
// pull latest version of container if we haven't already
if *t.Flags.SkipPull == false &&
task.Image != "" &&
!t.Cm.DidPull(task.Image) {
t.Cm.PullImage(task.Image)
}
if *t.Flags.DryRun == false {
// run task
if task.Async == true {
go t.runTask(&scope, &task, &action)
} else {
t.runTask(&scope, &task, &action)
}
time.Sleep(5 * time.Second)
} else if len(task.Command) > 1 {
// just print command output without actually running
fmt.Println(task.Image,
fmt.Sprintf("[wait:%t]", action.Wait),
strings.Join(task.Command, " "))
}
lastAction = action
}
```
Click here to show extra information the analyzer produced.
```
The following graphviz dot graph describes paths through the callgraph that could lead to a function calling a goroutine:
digraph G {
"(runTask, 3)" -> {"(Run, 1)";}
"(Run, 1)" -> {"(HandleResults, 2)";"(ProvideCouchbaseServers, 2)";}
"(HandleResults, 2)" -> {}
"(ProvideCouchbaseServers, 2)" -> {}
}
```
Leave a reaction on this issue to contribute to the project by classifying this instance as a **Bug** :-1:, **Mitigated** :+1:, or **Desirable Behavior** :rocket:
See the descriptions of the classifications [here](https://github.com/github-vet/rangeclosure-findings#how-can-i-help) for more information.
commit ID: 4fe65c69b3522a82e0795338308d4683f76c73dc
Contributor guide
No contributing guide indexed for this repository
Research direction
Start with lib/test.go lines 293-631, especially the action loop and the call at line 618. Follow the analyzer's reported path through runTask and Run, then determine whether the possible goroutine interaction is a bug, mitigated, or desirable behavior; completion is a justified classification supported by the code.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- go
- Domain
- testing, tooling
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Stale
- Clarity
- Needs clarification
- Newbie friendliness
- 25/100