From 5b0171cc0c2f41797812922f2a99c9c2d62ca240 Mon Sep 17 00:00:00 2001 From: ohm Date: Thu, 6 Mar 2025 16:11:07 +0400 Subject: [PATCH 01/11] added the report comparer --- analyzer/analyzer.go | 34 +++++++++++++++++ analyzer/opcode/opcode.go | 1 + analyzer/syscall/asm_syscall.go | 7 +++- analyzer/syscall/go_syscall.go | 6 ++- cmd/analyze.go | 50 ++++++++++++++++++++++++ comparer/json.go | 61 ++++++++++++++++++++++++++++++ profile/baselinereport/report.json | 1 + renderer/json.go | 3 +- renderer/renderer.go | 2 +- renderer/text.go | 2 +- 10 files changed, 160 insertions(+), 7 deletions(-) create mode 100644 comparer/json.go create mode 100644 profile/baselinereport/report.json diff --git a/analyzer/analyzer.go b/analyzer/analyzer.go index a0f5a58..c9ad0c5 100644 --- a/analyzer/analyzer.go +++ b/analyzer/analyzer.go @@ -1,6 +1,14 @@ // Package analyzer provides an interface for analyzing source code for compatibility issues. package analyzer +import ( + "crypto/md5" + "encoding/hex" + "fmt" + "sort" + "strings" +) + // Analyzer represents the interface for the analyzer. type Analyzer interface { // Analyze analyzes the provided source code and returns any issues found. @@ -26,6 +34,12 @@ type Issue struct { Severity IssueSeverity `json:"severity"` Impact string `json:"impact,omitempty"` Reference string `json:"reference,omitempty"` + Hash string `json:"hash"` +} + +func (i *Issue) PopulateHash() { + hash := md5.Sum([]byte(fmt.Sprintf("%s:%s", i.Message, i.CallStack.Hash()))) + i.Hash = hex.EncodeToString(hash[:]) } // CallStack represents a location in the code where the issue originates. @@ -37,6 +51,15 @@ type CallStack struct { CallStack *CallStack `json:"callStack,omitempty"` // The trace of calls leading to this source. } +func (src *CallStack) Hash() string { + sub := src.Function + if src.CallStack != nil { + sub = fmt.Sprintf("%s:%s", sub, src.CallStack.Hash()) + } + hash := md5.Sum([]byte(sub)) + return hex.EncodeToString(hash[:]) +} + // Copy creates a deep copy of the CallStack. func (src *CallStack) Copy() *CallStack { if src == nil { @@ -66,3 +89,14 @@ func (src *CallStack) AddCallStack(stack *CallStack) { } src.CallStack.AddCallStack(stack) } + +type Issues []*Issue + +func (issues Issues) Sort() { + sort.Slice(issues, func(i, j int) bool { + if issues[i].Severity != issues[j].Severity { + return issues[i].Severity < issues[j].Severity + } + return strings.Compare(issues[i].Hash, issues[j].Hash) < 0 + }) +} diff --git a/analyzer/opcode/opcode.go b/analyzer/opcode/opcode.go index 8e23166..01dd67b 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.PopulateHash() issues = append(issues, issue) } } diff --git a/analyzer/syscall/asm_syscall.go b/analyzer/syscall/asm_syscall.go index e6d61dc..8349422 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, - }) + } + issues = append(issues, issue) + issue.PopulateHash() + issues = append(issues, issue) } } } diff --git a/analyzer/syscall/go_syscall.go b/analyzer/syscall/go_syscall.go index db44e0b..c14f82b 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.PopulateHash() + issues = append(issues, issue) } return issues, nil diff --git a/cmd/analyze.go b/cmd/analyze.go index fedf4db..238b3b2 100644 --- a/cmd/analyze.go +++ b/cmd/analyze.go @@ -8,6 +8,7 @@ import ( "github.com/ChainSafe/vm-compat/analyzer" "github.com/ChainSafe/vm-compat/analyzer/opcode" "github.com/ChainSafe/vm-compat/analyzer/syscall" + "github.com/ChainSafe/vm-compat/comparer" "github.com/ChainSafe/vm-compat/disassembler" "github.com/ChainSafe/vm-compat/disassembler/manager" "github.com/ChainSafe/vm-compat/profile" @@ -50,6 +51,18 @@ var ( Required: false, Value: false, } + + BaselineReport = &cli.StringFlag{ + Name: "baseline-report", + Usage: "Path to the baseline report", + Required: false, + } + + CompareReportFlag = &cli.BoolFlag{ + Name: "compare-report", + Usage: "compare the current report with the baseline report", + Required: false, + } ) func CreateAnalyzeCommand(action cli.ActionFunc) *cli.Command { @@ -65,6 +78,8 @@ func CreateAnalyzeCommand(action cli.ActionFunc) *cli.Command { FormatFlag, ReportOutputPathFlag, TraceFlag, + CompareReportFlag, + BaselineReport, }, } } @@ -84,6 +99,8 @@ func AnalyzeCompatibility(ctx *cli.Context) error { reportOutputPath := ctx.Path(ReportOutputPathFlag.Name) analysisType := ctx.String(AnalysisTypeFlag.Name) withTrace := ctx.Bool(TraceFlag.Name) + withCompareReport := ctx.Bool(CompareReportFlag.Name) + baselineReport := ctx.Path(BaselineReport.Name) disassemblyPath, err = disassemble(prof, source, disassemblyPath) if err != nil { @@ -95,6 +112,17 @@ func AnalyzeCompatibility(ctx *cli.Context) error { return fmt.Errorf("analysis failed: %w", err) } + if withCompareReport { + if baselineReport == "" { + return fmt.Errorf("baseline report path is required for comparison") + } + err = compareReport(issues, format, reportOutputPath, prof, baselineReport) + if err != nil { + return fmt.Errorf("error comparing reports: %w", err) + } + return nil + } + if err := writeReport(issues, format, reportOutputPath, prof); err != nil { return fmt.Errorf("unable to write report: %w", err) } @@ -168,3 +196,25 @@ func writeReport(issues []*analyzer.Issue, format, outputPath string, prof *prof return rendererInstance.Render(issues, output) } + +func compareReport(issues []*analyzer.Issue, format, outputPath string, prof *profile.VMProfile, baselineReport string) error { + absPath, err := filepath.Abs(baselineReport) + if err != nil { + return fmt.Errorf("error determining absolute path of baseline report: %w", err) + } + + baselineIssuesFile, err := os.OpenFile(absPath, os.O_RDONLY, 0600) + if err != nil { + return fmt.Errorf("error loading baseline report: %w", err) + } + defer func() { + _ = baselineIssuesFile.Close() + }() + + issues, err = comparer.NewJSONComparer().CompareReport(issues, baselineIssuesFile) + if err != nil { + return fmt.Errorf("error comparing reports: %w", err) + } + + return writeReport(issues, format, outputPath, prof) +} diff --git a/comparer/json.go b/comparer/json.go new file mode 100644 index 0000000..ef2a4c0 --- /dev/null +++ b/comparer/json.go @@ -0,0 +1,61 @@ +package comparer + +import ( + "encoding/json" + "fmt" + "io" + "sort" + + "github.com/ChainSafe/vm-compat/analyzer" +) + +// Comparer defines the interface for comparing the issues +type Comparer interface { + CompareReport(issues []*analyzer.Issue, reader io.Reader) ([]*analyzer.Issue, error) + + Format() string +} + +type jsonComparer struct{} + +func NewJSONComparer() Comparer { + return &jsonComparer{} +} + +func (r *jsonComparer) CompareReport(issues []*analyzer.Issue, reader io.Reader) ([]*analyzer.Issue, error) { + baseLineIssues := make([]*analyzer.Issue, 0) + data, err := io.ReadAll(reader) + if err != nil { + return nil, fmt.Errorf("error reading data while decoding issues: %v", err) + } + err = json.Unmarshal(data, &baseLineIssues) + if err != nil { + return nil, fmt.Errorf("error decoding issues: %v", err) + } + + sort.Slice(issues, func(i, j int) bool { + if issues[i].Severity != issues[j].Severity { + return issues[i].Severity < issues[j].Severity + } + return issues[i].Hash < issues[j].Hash + }) + + sort.Slice(baseLineIssues, func(i, j int) bool { + if issues[i].Severity != issues[j].Severity { + return issues[i].Severity < issues[j].Severity + } + return issues[i].Hash < issues[j].Hash + }) + + newIssues := make([]*analyzer.Issue, 0) + for i, newIssue := range issues { + if i >= len(baseLineIssues) || newIssue.Hash != baseLineIssues[i].Hash { + newIssues = append(newIssues, newIssue) + } + } + return newIssues, nil +} + +func (r *jsonComparer) Format() string { + return "json" +} diff --git a/profile/baselinereport/report.json b/profile/baselinereport/report.json new file mode 100644 index 0000000..0637a08 --- /dev/null +++ b/profile/baselinereport/report.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/renderer/json.go b/renderer/json.go index 66e17e3..7f13a82 100644 --- a/renderer/json.go +++ b/renderer/json.go @@ -15,7 +15,8 @@ func NewJSONRenderer() Renderer { return &JSONRenderer{} } -func (r *JSONRenderer) Render(issues []*analyzer.Issue, output io.Writer) error { +func (r *JSONRenderer) Render(issues analyzer.Issues, output io.Writer) error { + issues.Sort() return json.NewEncoder(output).Encode(issues) } diff --git a/renderer/renderer.go b/renderer/renderer.go index 89b6014..6351a29 100644 --- a/renderer/renderer.go +++ b/renderer/renderer.go @@ -10,7 +10,7 @@ import ( // Renderer defines the interface for rendering lint results in different formats. type Renderer interface { // Render takes a list of issues and outputs them in the desired format to the provided writer. - Render(issues []*analyzer.Issue, output io.Writer) error + Render(issues analyzer.Issues, output io.Writer) error // Format returns the name of the output format (e.g., "json", "text", "html"). Format() string diff --git a/renderer/text.go b/renderer/text.go index 938c173..94cbf6d 100644 --- a/renderer/text.go +++ b/renderer/text.go @@ -24,7 +24,7 @@ func NewTextRenderer(profile *profile.VMProfile) Renderer { } // Render formats and writes the analysis report to the command line. -func (r *TextRenderer) Render(issues []*analyzer.Issue, output io.Writer) error { +func (r *TextRenderer) Render(issues analyzer.Issues, output io.Writer) error { if len(issues) == 0 { return nil } From baaff1ef04cb1cd4ab734ed1b9cc60a00abc7240 Mon Sep 17 00:00:00 2001 From: ohm Date: Thu, 6 Mar 2025 16:12:38 +0400 Subject: [PATCH 02/11] removed double append --- analyzer/syscall/asm_syscall.go | 1 - 1 file changed, 1 deletion(-) diff --git a/analyzer/syscall/asm_syscall.go b/analyzer/syscall/asm_syscall.go index 8349422..7bf53d4 100644 --- a/analyzer/syscall/asm_syscall.go +++ b/analyzer/syscall/asm_syscall.go @@ -87,7 +87,6 @@ func (a *asmSyscallAnalyser) Analyze(path string, withTrace bool) ([]*analyzer.I Impact: potentialImpactMsg, Reference: analyzerWorkingPrincipalURL, } - issues = append(issues, issue) issue.PopulateHash() issues = append(issues, issue) } From 40b9a3f004fed1be94c51a9e161fc74617bf625d Mon Sep 17 00:00:00 2001 From: ohm Date: Fri, 7 Mar 2025 14:56:20 +0400 Subject: [PATCH 03/11] replaced md5 with sha256 --- analyzer/analyzer.go | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/analyzer/analyzer.go b/analyzer/analyzer.go index c9ad0c5..d313a59 100644 --- a/analyzer/analyzer.go +++ b/analyzer/analyzer.go @@ -2,7 +2,7 @@ package analyzer import ( - "crypto/md5" + "crypto/sha256" "encoding/hex" "fmt" "sort" @@ -38,8 +38,9 @@ type Issue struct { } func (i *Issue) PopulateHash() { - hash := md5.Sum([]byte(fmt.Sprintf("%s:%s", i.Message, i.CallStack.Hash()))) - i.Hash = hex.EncodeToString(hash[:]) + h := sha256.New() + h.Write([]byte(fmt.Sprintf("%s:%s", i.Message, i.CallStack.Hash()))) + i.Hash = hex.EncodeToString(h.Sum(nil)) } // CallStack represents a location in the code where the issue originates. @@ -56,8 +57,9 @@ func (src *CallStack) Hash() string { if src.CallStack != nil { sub = fmt.Sprintf("%s:%s", sub, src.CallStack.Hash()) } - hash := md5.Sum([]byte(sub)) - return hex.EncodeToString(hash[:]) + h := sha256.New() + h.Write([]byte(sub)) + return hex.EncodeToString(h.Sum(nil)) } // Copy creates a deep copy of the CallStack. From 3d5657847b3c1f49745a979899ba401d1bbae6fc Mon Sep 17 00:00:00 2001 From: ohm Date: Fri, 7 Mar 2025 14:59:35 +0400 Subject: [PATCH 04/11] lint fixes --- analyzer/analyzer.go | 2 +- comparer/json.go | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/analyzer/analyzer.go b/analyzer/analyzer.go index d313a59..4e03190 100644 --- a/analyzer/analyzer.go +++ b/analyzer/analyzer.go @@ -39,7 +39,7 @@ type Issue struct { func (i *Issue) PopulateHash() { h := sha256.New() - h.Write([]byte(fmt.Sprintf("%s:%s", i.Message, i.CallStack.Hash()))) + _, _ = fmt.Fprintf(h, "%s:%s", i.Message, i.CallStack.Hash()) i.Hash = hex.EncodeToString(h.Sum(nil)) } diff --git a/comparer/json.go b/comparer/json.go index ef2a4c0..af02c94 100644 --- a/comparer/json.go +++ b/comparer/json.go @@ -1,3 +1,4 @@ +// Package comparer provides a way to compare current issues with the baseline report. package comparer import ( @@ -26,11 +27,11 @@ func (r *jsonComparer) CompareReport(issues []*analyzer.Issue, reader io.Reader) baseLineIssues := make([]*analyzer.Issue, 0) data, err := io.ReadAll(reader) if err != nil { - return nil, fmt.Errorf("error reading data while decoding issues: %v", err) + return nil, fmt.Errorf("error reading data while decoding issues: %w", err) } err = json.Unmarshal(data, &baseLineIssues) if err != nil { - return nil, fmt.Errorf("error decoding issues: %v", err) + return nil, fmt.Errorf("error decoding issues: %w", err) } sort.Slice(issues, func(i, j int) bool { From 3b8bf032262d67c5030e12e7e86d67837c62db62 Mon Sep 17 00:00:00 2001 From: ohm Date: Fri, 7 Mar 2025 15:45:56 +0400 Subject: [PATCH 05/11] changed addressed --- analyzer/analyzer.go | 13 ++++++------- profile/baselinereport/report.json | 1 - renderer/json.go | 3 +-- 3 files changed, 7 insertions(+), 10 deletions(-) delete mode 100644 profile/baselinereport/report.json diff --git a/analyzer/analyzer.go b/analyzer/analyzer.go index 4e03190..356db95 100644 --- a/analyzer/analyzer.go +++ b/analyzer/analyzer.go @@ -39,7 +39,7 @@ type Issue struct { func (i *Issue) PopulateHash() { h := sha256.New() - _, _ = fmt.Fprintf(h, "%s:%s", i.Message, i.CallStack.Hash()) + _, _ = fmt.Fprintf(h, "%s:%s", i.Message, i.CallStack.Trace()) i.Hash = hex.EncodeToString(h.Sum(nil)) } @@ -52,14 +52,12 @@ type CallStack struct { CallStack *CallStack `json:"callStack,omitempty"` // The trace of calls leading to this source. } -func (src *CallStack) Hash() string { +func (src *CallStack) Trace() string { sub := src.Function if src.CallStack != nil { - sub = fmt.Sprintf("%s:%s", sub, src.CallStack.Hash()) + sub = fmt.Sprintf("%s:%s", sub, src.CallStack.Trace()) } - h := sha256.New() - h.Write([]byte(sub)) - return hex.EncodeToString(h.Sum(nil)) + return sub } // Copy creates a deep copy of the CallStack. @@ -94,11 +92,12 @@ func (src *CallStack) AddCallStack(stack *CallStack) { type Issues []*Issue -func (issues Issues) Sort() { +func SortIssues(issues []*Issue) []*Issue { sort.Slice(issues, func(i, j int) bool { if issues[i].Severity != issues[j].Severity { return issues[i].Severity < issues[j].Severity } return strings.Compare(issues[i].Hash, issues[j].Hash) < 0 }) + return issues } diff --git a/profile/baselinereport/report.json b/profile/baselinereport/report.json deleted file mode 100644 index 0637a08..0000000 --- a/profile/baselinereport/report.json +++ /dev/null @@ -1 +0,0 @@ -[] \ No newline at end of file diff --git a/renderer/json.go b/renderer/json.go index 7f13a82..da95def 100644 --- a/renderer/json.go +++ b/renderer/json.go @@ -16,8 +16,7 @@ func NewJSONRenderer() Renderer { } func (r *JSONRenderer) Render(issues analyzer.Issues, output io.Writer) error { - issues.Sort() - return json.NewEncoder(output).Encode(issues) + return json.NewEncoder(output).Encode(analyzer.SortIssues(issues)) } func (r *JSONRenderer) Format() string { From f224a24ba93bba437abb8262c652946d25d0a0f4 Mon Sep 17 00:00:00 2001 From: ohm Date: Fri, 7 Mar 2025 15:56:38 +0400 Subject: [PATCH 06/11] removed slice --- analyzer/analyzer.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/analyzer/analyzer.go b/analyzer/analyzer.go index 356db95..13a1d04 100644 --- a/analyzer/analyzer.go +++ b/analyzer/analyzer.go @@ -90,8 +90,6 @@ func (src *CallStack) AddCallStack(stack *CallStack) { src.CallStack.AddCallStack(stack) } -type Issues []*Issue - func SortIssues(issues []*Issue) []*Issue { sort.Slice(issues, func(i, j int) bool { if issues[i].Severity != issues[j].Severity { From 0a0adc01fc50d72d8a8cea0446262a6a8d054763 Mon Sep 17 00:00:00 2001 From: ohm Date: Fri, 7 Mar 2025 15:56:50 +0400 Subject: [PATCH 07/11] lint fixes --- analyzer/analyzer.go | 1 + 1 file changed, 1 insertion(+) diff --git a/analyzer/analyzer.go b/analyzer/analyzer.go index 13a1d04..edfcd59 100644 --- a/analyzer/analyzer.go +++ b/analyzer/analyzer.go @@ -90,6 +90,7 @@ func (src *CallStack) AddCallStack(stack *CallStack) { src.CallStack.AddCallStack(stack) } +// SortIssues sorts the issues by severity and hash. func SortIssues(issues []*Issue) []*Issue { sort.Slice(issues, func(i, j int) bool { if issues[i].Severity != issues[j].Severity { From 394f5640ce96dffd4e2a8686b68f2828aaefd4a7 Mon Sep 17 00:00:00 2001 From: ohm Date: Fri, 7 Mar 2025 15:59:20 +0400 Subject: [PATCH 08/11] lint fixes --- renderer/json.go | 2 +- renderer/renderer.go | 2 +- renderer/text.go | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/renderer/json.go b/renderer/json.go index da95def..7641355 100644 --- a/renderer/json.go +++ b/renderer/json.go @@ -15,7 +15,7 @@ func NewJSONRenderer() Renderer { return &JSONRenderer{} } -func (r *JSONRenderer) Render(issues analyzer.Issues, output io.Writer) error { +func (r *JSONRenderer) Render(issues []*analyzer.Issue, output io.Writer) error { return json.NewEncoder(output).Encode(analyzer.SortIssues(issues)) } diff --git a/renderer/renderer.go b/renderer/renderer.go index 6351a29..89b6014 100644 --- a/renderer/renderer.go +++ b/renderer/renderer.go @@ -10,7 +10,7 @@ import ( // Renderer defines the interface for rendering lint results in different formats. type Renderer interface { // Render takes a list of issues and outputs them in the desired format to the provided writer. - Render(issues analyzer.Issues, output io.Writer) error + Render(issues []*analyzer.Issue, output io.Writer) error // Format returns the name of the output format (e.g., "json", "text", "html"). Format() string diff --git a/renderer/text.go b/renderer/text.go index 94cbf6d..938c173 100644 --- a/renderer/text.go +++ b/renderer/text.go @@ -24,7 +24,7 @@ func NewTextRenderer(profile *profile.VMProfile) Renderer { } // Render formats and writes the analysis report to the command line. -func (r *TextRenderer) Render(issues analyzer.Issues, output io.Writer) error { +func (r *TextRenderer) Render(issues []*analyzer.Issue, output io.Writer) error { if len(issues) == 0 { return nil } From a3358f08320b92f80b5fa8dc9f55f822930eeba4 Mon Sep 17 00:00:00 2001 From: ohm Date: Fri, 7 Mar 2025 16:18:59 +0400 Subject: [PATCH 09/11] reuse code --- cmd/analyze.go | 13 +------------ comparer/json.go | 16 ++-------------- 2 files changed, 3 insertions(+), 26 deletions(-) diff --git a/cmd/analyze.go b/cmd/analyze.go index 238b3b2..7a938db 100644 --- a/cmd/analyze.go +++ b/cmd/analyze.go @@ -57,12 +57,6 @@ var ( Usage: "Path to the baseline report", Required: false, } - - CompareReportFlag = &cli.BoolFlag{ - Name: "compare-report", - Usage: "compare the current report with the baseline report", - Required: false, - } ) func CreateAnalyzeCommand(action cli.ActionFunc) *cli.Command { @@ -78,7 +72,6 @@ func CreateAnalyzeCommand(action cli.ActionFunc) *cli.Command { FormatFlag, ReportOutputPathFlag, TraceFlag, - CompareReportFlag, BaselineReport, }, } @@ -99,7 +92,6 @@ func AnalyzeCompatibility(ctx *cli.Context) error { reportOutputPath := ctx.Path(ReportOutputPathFlag.Name) analysisType := ctx.String(AnalysisTypeFlag.Name) withTrace := ctx.Bool(TraceFlag.Name) - withCompareReport := ctx.Bool(CompareReportFlag.Name) baselineReport := ctx.Path(BaselineReport.Name) disassemblyPath, err = disassemble(prof, source, disassemblyPath) @@ -112,10 +104,7 @@ func AnalyzeCompatibility(ctx *cli.Context) error { return fmt.Errorf("analysis failed: %w", err) } - if withCompareReport { - if baselineReport == "" { - return fmt.Errorf("baseline report path is required for comparison") - } + if baselineReport != "" { err = compareReport(issues, format, reportOutputPath, prof, baselineReport) if err != nil { return fmt.Errorf("error comparing reports: %w", err) diff --git a/comparer/json.go b/comparer/json.go index af02c94..9f18cf7 100644 --- a/comparer/json.go +++ b/comparer/json.go @@ -5,7 +5,6 @@ import ( "encoding/json" "fmt" "io" - "sort" "github.com/ChainSafe/vm-compat/analyzer" ) @@ -34,19 +33,8 @@ func (r *jsonComparer) CompareReport(issues []*analyzer.Issue, reader io.Reader) return nil, fmt.Errorf("error decoding issues: %w", err) } - sort.Slice(issues, func(i, j int) bool { - if issues[i].Severity != issues[j].Severity { - return issues[i].Severity < issues[j].Severity - } - return issues[i].Hash < issues[j].Hash - }) - - sort.Slice(baseLineIssues, func(i, j int) bool { - if issues[i].Severity != issues[j].Severity { - return issues[i].Severity < issues[j].Severity - } - return issues[i].Hash < issues[j].Hash - }) + issues = analyzer.SortIssues(issues) + baseLineIssues = analyzer.SortIssues(baseLineIssues) newIssues := make([]*analyzer.Issue, 0) for i, newIssue := range issues { From dc307a0d70ac9eaf003e3929b9f6c285dc77d6d2 Mon Sep 17 00:00:00 2001 From: ohm Date: Fri, 7 Mar 2025 17:24:49 +0400 Subject: [PATCH 10/11] cehck for existence --- comparer/json.go | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/comparer/json.go b/comparer/json.go index 9f18cf7..20dfaaa 100644 --- a/comparer/json.go +++ b/comparer/json.go @@ -33,12 +33,14 @@ func (r *jsonComparer) CompareReport(issues []*analyzer.Issue, reader io.Reader) return nil, fmt.Errorf("error decoding issues: %w", err) } - issues = analyzer.SortIssues(issues) - baseLineIssues = analyzer.SortIssues(baseLineIssues) + baselineIssuesMap := make(map[string]*analyzer.Issue) + for _, issue := range baseLineIssues { + baselineIssuesMap[issue.Hash] = issue + } newIssues := make([]*analyzer.Issue, 0) - for i, newIssue := range issues { - if i >= len(baseLineIssues) || newIssue.Hash != baseLineIssues[i].Hash { + for _, newIssue := range issues { + if _, ok := baselineIssuesMap[newIssue.Hash]; !ok { newIssues = append(newIssues, newIssue) } } From b066618b7676e49300f89da63ff30bc6735dbe7f Mon Sep 17 00:00:00 2001 From: ohm Date: Fri, 7 Mar 2025 18:03:57 +0400 Subject: [PATCH 11/11] refactor code --- analyzer/analyzer.go | 49 +++++++++++++++++++++++++++++++++ analyzer/opcode/opcode.go | 19 +++++++------ analyzer/syscall/asm_syscall.go | 17 ++++++------ analyzer/syscall/go_syscall.go | 15 +++++----- 4 files changed, 76 insertions(+), 24 deletions(-) diff --git a/analyzer/analyzer.go b/analyzer/analyzer.go index edfcd59..a339330 100644 --- a/analyzer/analyzer.go +++ b/analyzer/analyzer.go @@ -37,6 +37,55 @@ type Issue struct { Hash string `json:"hash"` } +// Opt is a functional option for configuring an Issue. +type Opt func(*Issue) + +// WithImpact sets the impact of the issue. +func WithImpact(impact string) Opt { + return func(i *Issue) { + i.Impact = impact + } +} + +// WithReference sets the reference for the issue. +func WithReference(reference string) Opt { + return func(i *Issue) { + i.Reference = reference + } +} + +// WithCallStack sets the call stack for the issue. +func WithCallStack(callStack *CallStack) Opt { + return func(i *Issue) { + i.CallStack = callStack + } +} + +// WithSeverity sets the severity for the issue. +func WithSeverity(severity IssueSeverity) Opt { + return func(i *Issue) { + i.Severity = severity + } +} + +// WithMessage sets the message for the issue. +func WithMessage(message string) Opt { + return func(i *Issue) { + i.Message = message + } +} + +// NewIssue creates a new issue with the provided severity, message, and source. +func NewIssue(opts ...Opt) *Issue { + issue := new(Issue) + for _, opt := range opts { + opt(issue) + } + issue.PopulateHash() + return issue +} + +// PopulateHash generates a hash for the issue and populates it into the issue func (i *Issue) PopulateHash() { h := sha256.New() _, _ = fmt.Fprintf(h, "%s:%s", i.Message, i.CallStack.Trace()) diff --git a/analyzer/opcode/opcode.go b/analyzer/opcode/opcode.go index 01dd67b..f3d7afa 100644 --- a/analyzer/opcode/opcode.go +++ b/analyzer/opcode/opcode.go @@ -45,20 +45,23 @@ func (op *opcode) Analyze(path string, withTrace bool) ([]*analyzer.Issue, error if err != nil { // non-reachable portion ignored continue } - issue := &analyzer.Issue{ - Severity: analyzer.IssueSeverityCritical, - CallStack: source, - Message: fmt.Sprintf("Potential Incompatible Opcode Detected: Opcode: %s, Funct: %s", - instruction.OpcodeHex(), instruction.Funct()), + + opts := []analyzer.Opt{ + analyzer.WithSeverity(analyzer.IssueSeverityCritical), + analyzer.WithCallStack(source), + analyzer.WithMessage( + fmt.Sprintf("Potential Incompatible Opcode Detected: Opcode: %s, Funct: %s", + instruction.OpcodeHex(), instruction.Funct()), + ), } if common.ShouldIgnoreSource(source, op.profile.IgnoredFunctions) { - issue.Severity = analyzer.IssueSeverityWarning + opts = append(opts, analyzer.WithSeverity(analyzer.IssueSeverityWarning)) } if !withTrace { source.CallStack = nil } - issue.PopulateHash() - issues = append(issues, issue) + + issues = append(issues, analyzer.NewIssue(opts...)) } } } diff --git a/analyzer/syscall/asm_syscall.go b/analyzer/syscall/asm_syscall.go index 7bf53d4..e34b11d 100644 --- a/analyzer/syscall/asm_syscall.go +++ b/analyzer/syscall/asm_syscall.go @@ -80,15 +80,14 @@ func (a *asmSyscallAnalyser) Analyze(path string, withTrace bool) ([]*analyzer.I if !withTrace { source.CallStack = nil } - issue := &analyzer.Issue{ - Severity: severity, - Message: message, - CallStack: source, - Impact: potentialImpactMsg, - Reference: analyzerWorkingPrincipalURL, - } - issue.PopulateHash() - issues = append(issues, issue) + + issues = append(issues, analyzer.NewIssue( + analyzer.WithImpact(potentialImpactMsg), + analyzer.WithReference(analyzerWorkingPrincipalURL), + analyzer.WithCallStack(source), + analyzer.WithSeverity(severity), + analyzer.WithMessage(message), + )) } } } diff --git a/analyzer/syscall/go_syscall.go b/analyzer/syscall/go_syscall.go index c14f82b..61a607a 100644 --- a/analyzer/syscall/go_syscall.go +++ b/analyzer/syscall/go_syscall.go @@ -63,13 +63,14 @@ func (a *goSyscallAnalyser) Analyze(path string, withTrace bool) ([]*analyzer.Is message = fmt.Sprintf("Potential NOOP Syscall Detected: %d", syscll.num) } - issue := &analyzer.Issue{ - Severity: severity, - CallStack: stackTrace, - Message: message, - } - issue.PopulateHash() - issues = append(issues, issue) + issues = append( + issues, + analyzer.NewIssue( + analyzer.WithSeverity(severity), + analyzer.WithCallStack(stackTrace), + analyzer.WithMessage(message), + ), + ) } return issues, nil