diff --git a/analyzer/analyzer.go b/analyzer/analyzer.go index a0f5a58..9cf1feb 100644 --- a/analyzer/analyzer.go +++ b/analyzer/analyzer.go @@ -1,6 +1,11 @@ // Package analyzer provides an interface for analyzing source code for compatibility issues. package analyzer +import ( + "crypto/sha256" + "encoding/hex" +) + // Analyzer represents the interface for the analyzer. type Analyzer interface { // Analyze analyzes the provided source code and returns any issues found. @@ -26,6 +31,17 @@ type Issue struct { Severity IssueSeverity `json:"severity"` Impact string `json:"impact,omitempty"` Reference string `json:"reference,omitempty"` + Hash string `json:"hash,omitempty"` +} + +func (i *Issue) CalculateHash() { + hashString := i.Message + if i.CallStack != nil { + hashString = hashString + "::" + i.CallStack.getHashString() + } + h := sha256.New() + h.Write([]byte(hashString)) + i.Hash = hex.EncodeToString(h.Sum(nil)) } // CallStack represents a location in the code where the issue originates. @@ -37,6 +53,14 @@ type CallStack struct { CallStack *CallStack `json:"callStack,omitempty"` // The trace of calls leading to this source. } +func (src *CallStack) getHashString() string { + hashString := src.Function + if src.CallStack != nil { + hashString = hashString + "<-" + src.CallStack.getHashString() + } + return hashString +} + // Copy creates a deep copy of the CallStack. func (src *CallStack) Copy() *CallStack { if src == nil { diff --git a/analyzer/opcode/opcode.go b/analyzer/opcode/opcode.go index 8e23166..033cc43 100644 --- a/analyzer/opcode/opcode.go +++ b/analyzer/opcode/opcode.go @@ -57,6 +57,7 @@ func (op *opcode) Analyze(path string, withTrace bool) ([]*analyzer.Issue, error if !withTrace { source.CallStack = nil } + issue.CalculateHash() issues = append(issues, issue) } } diff --git a/analyzer/syscall/asm_syscall.go b/analyzer/syscall/asm_syscall.go index e6d61dc..894dc97 100644 --- a/analyzer/syscall/asm_syscall.go +++ b/analyzer/syscall/asm_syscall.go @@ -80,13 +80,16 @@ func (a *asmSyscallAnalyser) Analyze(path string, withTrace bool) ([]*analyzer.I if !withTrace { source.CallStack = nil } - issues = append(issues, &analyzer.Issue{ + + issue := &analyzer.Issue{ Severity: severity, Message: message, CallStack: source, Impact: potentialImpactMsg, Reference: analyzerWorkingPrincipalURL, - }) + } + issue.CalculateHash() + issues = append(issues, issue) } } } diff --git a/analyzer/syscall/go_syscall.go b/analyzer/syscall/go_syscall.go index db44e0b..99e8629 100644 --- a/analyzer/syscall/go_syscall.go +++ b/analyzer/syscall/go_syscall.go @@ -63,11 +63,13 @@ func (a *goSyscallAnalyser) Analyze(path string, withTrace bool) ([]*analyzer.Is message = fmt.Sprintf("Potential NOOP Syscall Detected: %d", syscll.num) } - issues = append(issues, &analyzer.Issue{ + issue := &analyzer.Issue{ Severity: severity, CallStack: stackTrace, Message: message, - }) + } + issue.CalculateHash() + issues = append(issues, issue) } return issues, nil diff --git a/renderer/json.go b/renderer/json.go index 66e17e3..a4c6065 100644 --- a/renderer/json.go +++ b/renderer/json.go @@ -4,6 +4,7 @@ package renderer import ( "encoding/json" "io" + "sort" "github.com/ChainSafe/vm-compat/analyzer" ) @@ -16,6 +17,14 @@ func NewJSONRenderer() Renderer { } func (r *JSONRenderer) Render(issues []*analyzer.Issue, output io.Writer) error { + sort.Slice(issues, func(i, j int) bool { + // Sort by severity first + if issues[i].Severity != issues[j].Severity { + return issues[i].Severity < issues[j].Severity + } + // If severity is the same, sort by hash lexicographically + return issues[i].Hash < issues[j].Hash + }) return json.NewEncoder(output).Encode(issues) }