-
Notifications
You must be signed in to change notification settings - Fork 0
Refactor opengrep runner to stream JSON output #26
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -50,14 +50,9 @@ | |||||||||||||||||||||||||||
| func executeCommandForFiles(configurationFile *os.File, toolExecution codacy.ToolExecution, patternDescriptions *[]codacy.PatternDescription, language string, files []string) ([]codacy.Result, error) { | ||||||||||||||||||||||||||||
| semgrepCmd := createCommand(configurationFile, toolExecution.SourceDir, language, files) | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| semgrepOutput, semgrepError, err := runCommand(semgrepCmd) | ||||||||||||||||||||||||||||
| output, semgrepError, err := runAndParseCommand(semgrepCmd, patternDescriptions) | ||||||||||||||||||||||||||||
| if err != nil { | ||||||||||||||||||||||||||||
| return nil, errors.New("Error running semgrep: " + *semgrepError + "\n" + err.Error()) | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| output, err := parseCommandOutput(patternDescriptions, *semgrepOutput) | ||||||||||||||||||||||||||||
| if err != nil { | ||||||||||||||||||||||||||||
| return nil, err | ||||||||||||||||||||||||||||
| return nil, errors.New("Error running semgrep: " + semgrepError + "\n" + err.Error()) | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
| return output, nil | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
|
|
@@ -104,32 +99,58 @@ | |||||||||||||||||||||||||||
| return cmdParams | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| func runCommand(cmd *exec.Cmd) (*string, *string, error) { | ||||||||||||||||||||||||||||
| var stderr bytes.Buffer | ||||||||||||||||||||||||||||
| cmd.Stderr = &stderr | ||||||||||||||||||||||||||||
| cmdOutput, err := cmd.Output() | ||||||||||||||||||||||||||||
| func runAndParseCommand(cmd *exec.Cmd, patternDescriptions *[]codacy.PatternDescription) ([]codacy.Result, string, error) { | ||||||||||||||||||||||||||||
| stdoutPipe, err := cmd.StdoutPipe() | ||||||||||||||||||||||||||||
| if err != nil { | ||||||||||||||||||||||||||||
| stderrString := stderr.String() | ||||||||||||||||||||||||||||
| return nil, &stderrString, err | ||||||||||||||||||||||||||||
| return nil, "", err | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| stderrPipe, err := cmd.StderrPipe() | ||||||||||||||||||||||||||||
| if err != nil { | ||||||||||||||||||||||||||||
| return nil, "", err | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| stderrTail := &limitedBuffer{max: maxStderrBytes} | ||||||||||||||||||||||||||||
| stderrDone := make(chan error, 1) | ||||||||||||||||||||||||||||
| go func() { | ||||||||||||||||||||||||||||
| _, copyErr := io.Copy(stderrTail, stderrPipe) | ||||||||||||||||||||||||||||
| stderrDone <- copyErr | ||||||||||||||||||||||||||||
| }() | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| if err := cmd.Start(); err != nil { | ||||||||||||||||||||||||||||
| return nil, "", err | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| results, parseErr := parseCommandOutput(patternDescriptions, stdoutPipe) | ||||||||||||||||||||||||||||
| if parseErr != nil && cmd.Process != nil { | ||||||||||||||||||||||||||||
| _ = cmd.Process.Kill() | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| waitErr := cmd.Wait() | ||||||||||||||||||||||||||||
| stderrCopyErr := <-stderrDone | ||||||||||||||||||||||||||||
| if stderrCopyErr != nil && !isBenignStreamClose(stderrCopyErr) { | ||||||||||||||||||||||||||||
| return nil, stderrTail.String(), stderrCopyErr | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
| if parseErr != nil { | ||||||||||||||||||||||||||||
| return nil, stderrTail.String(), parseErr | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
| cmdOutputString := string(cmdOutput) | ||||||||||||||||||||||||||||
| return &cmdOutputString, nil, nil | ||||||||||||||||||||||||||||
| if waitErr != nil { | ||||||||||||||||||||||||||||
| return nil, stderrTail.String(), waitErr | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| return results, "", nil | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| func parseCommandOutput(patternDescriptions *[]codacy.PatternDescription, commandOutput string) ([]codacy.Result, error) { | ||||||||||||||||||||||||||||
| func parseCommandOutput(patternDescriptions *[]codacy.PatternDescription, stream io.Reader) ([]codacy.Result, error) { | ||||||||||||||||||||||||||||
| var result []codacy.Result | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| // Convert the JSON string to a []byte slice | ||||||||||||||||||||||||||||
| jsonData := []byte(commandOutput) | ||||||||||||||||||||||||||||
| // Create a bytes.Reader from the []byte slice | ||||||||||||||||||||||||||||
| reader := bytes.NewReader(jsonData) | ||||||||||||||||||||||||||||
| // Create a JSON decoder | ||||||||||||||||||||||||||||
| decoder := json.NewDecoder(reader) | ||||||||||||||||||||||||||||
| decoder := json.NewDecoder(stream) | ||||||||||||||||||||||||||||
| // Read and process the JSON stream | ||||||||||||||||||||||||||||
| for { | ||||||||||||||||||||||||||||
| var semgrepOutput SemgrepOutput // or a struct that matches your JSON structure | ||||||||||||||||||||||||||||
| if err := decoder.Decode(&semgrepOutput); err != nil { | ||||||||||||||||||||||||||||
| if err == io.EOF { | ||||||||||||||||||||||||||||
| if isBenignStreamClose(err) { | ||||||||||||||||||||||||||||
| break // End of input | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
| return nil, err | ||||||||||||||||||||||||||||
|
|
@@ -143,6 +164,40 @@ | |||||||||||||||||||||||||||
| return result, nil | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| const maxStderrBytes = 64 * 1024 | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| type limitedBuffer struct { | ||||||||||||||||||||||||||||
| buf bytes.Buffer | ||||||||||||||||||||||||||||
| max int | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| func (l *limitedBuffer) Write(p []byte) (int, error) { | ||||||||||||||||||||||||||||
| if l.max <= 0 { | ||||||||||||||||||||||||||||
| return len(p), nil | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
| if len(p) >= l.max { | ||||||||||||||||||||||||||||
| l.buf.Reset() | ||||||||||||||||||||||||||||
| _, _ = l.buf.Write(p[len(p)-l.max:]) | ||||||||||||||||||||||||||||
| return len(p), nil | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
| if l.buf.Len()+len(p) > l.max { | ||||||||||||||||||||||||||||
| drop := l.buf.Len() + len(p) - l.max | ||||||||||||||||||||||||||||
| current := l.buf.Bytes() | ||||||||||||||||||||||||||||
| l.buf.Reset() | ||||||||||||||||||||||||||||
| _, _ = l.buf.Write(current[drop:]) | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
|
Comment on lines
+183
to
+188
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The current implementation of
Suggested change
|
||||||||||||||||||||||||||||
| _, _ = l.buf.Write(p) | ||||||||||||||||||||||||||||
| return len(p), nil | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| func (l *limitedBuffer) String() string { | ||||||||||||||||||||||||||||
| return l.buf.String() | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| func isBenignStreamClose(err error) bool { | ||||||||||||||||||||||||||||
| return errors.Is(err, io.EOF) || errors.Is(err, os.ErrClosed) || strings.Contains(err.Error(), "file already closed") | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
|
Comment on lines
+197
to
+199
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If func isBenignStreamClose(err error) bool {
if err == nil {
return false
}
return errors.Is(err, io.EOF) || errors.Is(err, os.ErrClosed) || strings.Contains(err.Error(), "file already closed")
} |
||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| func appendIssueToResult(result []codacy.Result, patternDescriptions *[]codacy.PatternDescription, semgrepOutput SemgrepOutput) []codacy.Result { | ||||||||||||||||||||||||||||
| for _, semgrepRes := range semgrepOutput.Results { | ||||||||||||||||||||||||||||
| if semgrepRes.Extra.IsIgnored { | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Checking
stderrCopyErrbeforeparseErrcan mask the actual parsing error. IfparseCommandOutputfails (e.g., due to invalid JSON), the process is killed, which often causesio.Copyon the stderr pipe to fail with a non-benign error (like a closed pipe). If this happens, the secondarystderrCopyErrwill be returned instead of the root causeparseErr, making debugging extremely difficult. We should check and returnparseErrfirst.