Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .golangci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ linters:

settings:
gocyclo:
min-complexity: 25
min-complexity: 15
gosec:
excludes:
- G501
Expand Down
62 changes: 32 additions & 30 deletions cmd/solo/subcommand/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,35 +72,6 @@ func NewRootCommand(soloCtx *context.CliContext) (*cobra.Command, func() error)
var memProfile string
var cpuFile *os.File

stopProfilingCallback := func() error {
pprof.StopCPUProfile()

if cpuFile != nil {
_ = cpuFile.Close()
}

if memProfile != "" {
runtime.GC()

f, err := os.OpenFile(memProfile, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
if err != nil {
return fmt.Errorf("failed to create memory profile %q: %v", memProfile, err)
}

defer func() {
if closeErr := f.Close(); closeErr != nil {
_, _ = fmt.Fprintf(os.Stderr, "failed to close memory profile %q: %v\n", memProfile, closeErr)
}
}()

if err := pprof.WriteHeapProfile(f); err != nil {
return fmt.Errorf("failed to write memory profile %q: %v", memProfile, err)
}
}

return nil
}

cmd := &cobra.Command{
Use: "solo",
SilenceUsage: true,
Expand Down Expand Up @@ -156,5 +127,36 @@ func NewRootCommand(soloCtx *context.CliContext) (*cobra.Command, func() error)
cmd.PersistentFlags().StringVar(&cpuProfile, "cpu-profile", "", "Enable CPU profiling and write data to the supplied file")
cmd.PersistentFlags().StringVar(&memProfile, "mem-profile", "", "Enable memory profiling and write data to the supplied file")

return cmd, stopProfilingCallback
return cmd, stopProfilingCallback(cpuFile, memProfile)
}

func stopProfilingCallback(cpuFile *os.File, memProfile string) func() error {
return func() error {
pprof.StopCPUProfile()

if cpuFile != nil {
_ = cpuFile.Close()
}

if memProfile != "" {
runtime.GC()

f, err := os.OpenFile(memProfile, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
if err != nil {
return fmt.Errorf("failed to create memory profile %q: %v", memProfile, err)
}

defer func() {
if closeErr := f.Close(); closeErr != nil {
_, _ = fmt.Fprintf(os.Stderr, "failed to close memory profile %q: %v\n", memProfile, closeErr)
}
}()

if err := pprof.WriteHeapProfile(f); err != nil {
return fmt.Errorf("failed to write memory profile %q: %v", memProfile, err)
}
}

return nil
}
}
111 changes: 59 additions & 52 deletions internal/pkg/architecture_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -196,73 +196,80 @@ func (t *ArchitectureTestSuite) TestConstructorsDontReturnInterfaces() {
for _, subDirectory := range []string{"cmd", "internal", "test"} {
scanPath := filepath.Join(t.projectRoot, subDirectory)

err := filepath.WalkDir(scanPath, func(path string, entry os.DirEntry, err error) error {
if err != nil || entry.IsDir() {
return err
}
err := filepath.WalkDir(scanPath, t.checkFileForInterfaceReturnTypeFunc(fileset, &violations))

if !strings.HasSuffix(path, ".go") || strings.HasSuffix(path, "_test.go") {
return nil
}
if err != nil {
t.FailNowf("failed to check constructor functions for returning interfaces", "%v", err)
}
}

parsedFile, err := parser.ParseFile(fileset, path, nil, parser.SkipObjectResolution)
if err != nil {
return err
}
for _, v := range violations {
t.Failf("Interface type returned from constructor function", "%s", v)
}
}

imports := buildImportAliasMap(parsedFile)
func (t *ArchitectureTestSuite) checkFileForInterfaceReturnTypeFunc(
fileset *token.FileSet,
violations *[]string,
) func(path string, entry os.DirEntry, err error) error {
return func(path string, entry os.DirEntry, err error) error {
if err != nil || entry.IsDir() {
return err
}

for _, declaration := range parsedFile.Decls {
// Ignore non-function declarations
fn, ok := declaration.(*ast.FuncDecl)
if !ok {
continue
}
if !strings.HasSuffix(path, ".go") || strings.HasSuffix(path, "_test.go") {
return nil
}

// Ignore non-constructor functions
if !strings.HasPrefix(fn.Name.Name, "New") {
continue
}
parsedFile, err := parser.ParseFile(fileset, path, nil, parser.SkipObjectResolution)
if err != nil {
return err
}

// Ignore methods on structs
if fn.Recv != nil && fn.Recv.NumFields() > 0 {
continue
}
imports := buildImportAliasMap(parsedFile)

// Ignore functions that return nothing
if fn.Type.Results == nil {
continue
}
for _, declaration := range parsedFile.Decls {
// Ignore non-function declarations
fn, ok := declaration.(*ast.FuncDecl)
if !ok {
continue
}

// For each returned parameter
for _, result := range fn.Type.Results.List {
pkgPath, isInterface, err := t.resolveInterfaceTypeInPackage(result.Type, imports)
if err != nil {
t.FailNowf("failed to resolve types package", "%v", err)
}
// Ignore non-constructor functions
if !strings.HasPrefix(fn.Name.Name, "New") {
continue
}

if isInterface {
pos := fileset.Position(fn.Pos())
filePath := strings.TrimPrefix(pos.Filename, t.projectRoot+string(filepath.Separator))
// Ignore methods on structs
if fn.Recv != nil && fn.Recv.NumFields() > 0 {
continue
}

violations = append(violations, fmt.Sprintf(
"%s:%d: %s() returns interface type from %s",
filePath, pos.Line, fn.Name.Name, pkgPath,
))
}
}
// Ignore functions that return nothing
if fn.Type.Results == nil {
continue
}

return nil
})
// For each returned parameter
for _, result := range fn.Type.Results.List {
pkgPath, isInterface, err := t.resolveInterfaceTypeInPackage(result.Type, imports)
if err != nil {
t.FailNowf("failed to resolve types package", "%v", err)
}

if err != nil {
t.FailNowf("failed to check constructor functions for returning interfaces", "%v", err)
if isInterface {
pos := fileset.Position(fn.Pos())
filePath := strings.TrimPrefix(pos.Filename, t.projectRoot+string(filepath.Separator))

*violations = append(*violations, fmt.Sprintf(
"%s:%d: %s() returns interface type from %s",
filePath, pos.Line, fn.Name.Name, pkgPath,
))
}
}
}
}

for _, v := range violations {
t.Failf("Interface type returned from constructor function", "%s", v)
return nil
}
}

Expand Down
103 changes: 103 additions & 0 deletions internal/pkg/common/app/cmd/exec_command_args_extractor.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
package cmd

import (
"strings"
)

type execCommandArgsExtractor struct {
extracted []string
current strings.Builder
escaped bool
singleQuoted bool
doubleQuoted bool
}

func newExecCommandArgsExtractor() *execCommandArgsExtractor {
return &execCommandArgsExtractor{
extracted: []string{},
current: strings.Builder{},
escaped: false,
singleQuoted: false,
doubleQuoted: false,
}
}

func (t *execCommandArgsExtractor) extractExecCommandArgs(command string) (string, []string) {
for _, char := range command {
t.extractExecCommandChar(char)
}

if t.current.Len() > 0 {
t.extracted = append(t.extracted, t.current.String())
}

return t.extracted[0], t.extracted[1:]
}

func (t *execCommandArgsExtractor) extractExecCommandChar(char int32) {
if t.isBackslash(char) {
t.extractBackslash()
} else if t.isDoubleQuote(char) {
t.extractDoubleQuote()
} else if t.isSingleQuote(char) {
t.extractSingleQuote()
} else if t.isSpace(char) {
t.extractSpace()
} else {
t.extractChar(char)
}
}

func (t *execCommandArgsExtractor) isBackslash(char int32) bool {
return char == '\\' && !t.escaped && !t.singleQuoted && !t.doubleQuoted
}

func (t *execCommandArgsExtractor) extractBackslash() {
t.escaped = true
}

func (t *execCommandArgsExtractor) isDoubleQuote(char int32) bool {
return char == '"' && !t.escaped && !t.singleQuoted
}

func (t *execCommandArgsExtractor) extractDoubleQuote() {
if t.doubleQuoted {
t.doubleQuoted = false

t.extracted = append(t.extracted, t.current.String())
t.current.Reset()
} else {
t.doubleQuoted = true
}
}

func (t *execCommandArgsExtractor) isSingleQuote(char int32) bool {
return char == '\'' && !t.escaped && !t.doubleQuoted
}

func (t *execCommandArgsExtractor) extractSingleQuote() {
if t.singleQuoted {
t.singleQuoted = false

t.extracted = append(t.extracted, t.current.String())
t.current.Reset()
} else {
t.singleQuoted = true
}
}

func (t *execCommandArgsExtractor) isSpace(char int32) bool {
return char == ' ' && !t.escaped && !t.singleQuoted && !t.doubleQuoted
}

func (t *execCommandArgsExtractor) extractSpace() {
if t.current.Len() > 0 {
t.extracted = append(t.extracted, t.current.String())
t.current.Reset()
}
}

func (t *execCommandArgsExtractor) extractChar(char int32) {
t.current.WriteRune(char)
t.escaped = false
}
48 changes: 1 addition & 47 deletions internal/pkg/common/app/cmd/split_command.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,59 +7,13 @@ func SplitCommand(shell string, command string) (string, []string) {
// and at least one character for the binary
if len(command) >= 2 && strings.HasPrefix(command, "/") {
// Exec format
return extractExecCommandArgs(command)
return newExecCommandArgsExtractor().extractExecCommandArgs(command)
}

// Shell format
return extractShellCommandArgs(shell, command)
}

func extractExecCommandArgs(command string) (string, []string) {
var extracted []string
var current strings.Builder
escaped := false
singleQuoted := false
doubleQuoted := false

for _, char := range command {
if char == '\\' && !escaped && !singleQuoted && !doubleQuoted {
escaped = true
} else if char == '"' && !escaped && !singleQuoted {
if doubleQuoted {
doubleQuoted = false

extracted = append(extracted, current.String())
current.Reset()
} else {
doubleQuoted = true
}
} else if char == '\'' && !escaped && !doubleQuoted {
if singleQuoted {
singleQuoted = false

extracted = append(extracted, current.String())
current.Reset()
} else {
singleQuoted = true
}
} else if char == ' ' && !escaped && !singleQuoted && !doubleQuoted {
if current.Len() > 0 {
extracted = append(extracted, current.String())
current.Reset()
}
} else {
current.WriteRune(char)
escaped = false
}
}

if current.Len() > 0 {
extracted = append(extracted, current.String())
}

return extracted[0], extracted[1:]
}

func extractShellCommandArgs(shell string, command string) (string, []string) {
return shell, []string{"-c", command}
}
Loading