From 5ac760814d85e1ee436875291ad5d3b96b84156d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E4=BD=B3=E6=BE=88520370910044?= Date: Thu, 30 May 2024 11:12:30 +0800 Subject: [PATCH] feat: cppcheck parser (#28) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: zjc_he Reviewed-on: https://focs.ji.sjtu.edu.cn/git/FOCS-dev/JOJ3/pulls/28 Reviewed-by: 张泊明518370910136 Co-authored-by: 张佳澈520370910044 Co-committed-by: 张佳澈520370910044 --- .gitmodules | 4 ++ examples/cppcheck/sillycode | 1 + internal/parsers/all.go | 1 + internal/parsers/cppcheck/meta.go | 9 +++ internal/parsers/cppcheck/parser.go | 85 +++++++++++++++++++++++++++++ internal/parsers/cppcheck/score.go | 66 ++++++++++++++++++++++ 6 files changed, 166 insertions(+) create mode 160000 examples/cppcheck/sillycode create mode 100644 internal/parsers/cppcheck/meta.go create mode 100644 internal/parsers/cppcheck/parser.go create mode 100644 internal/parsers/cppcheck/score.go diff --git a/.gitmodules b/.gitmodules index 71c5721..d6f32b0 100644 --- a/.gitmodules +++ b/.gitmodules @@ -30,3 +30,7 @@ path = examples/keyword/clangtidy/sillycode url = ssh://git@focs.ji.sjtu.edu.cn:2222/FOCS-dev/JOJ3-examples.git branch = keyword/clangtidy/sillycode +[submodule "examples/cppcheck/sillycode"] + path = examples/cppcheck/sillycode + url = ssh://git@focs.ji.sjtu.edu.cn:2222/FOCS-dev/JOJ3-examples.git + branch = cppcheck/sillycode diff --git a/examples/cppcheck/sillycode b/examples/cppcheck/sillycode new file mode 160000 index 0000000..0815ab9 --- /dev/null +++ b/examples/cppcheck/sillycode @@ -0,0 +1 @@ +Subproject commit 0815ab90d72641fc274231c075282567e9e17865 diff --git a/internal/parsers/all.go b/internal/parsers/all.go index 4decc13..615f90c 100644 --- a/internal/parsers/all.go +++ b/internal/parsers/all.go @@ -2,6 +2,7 @@ package parsers import ( _ "focs.ji.sjtu.edu.cn/git/FOCS-dev/JOJ3/internal/parsers/clangtidy" + _ "focs.ji.sjtu.edu.cn/git/FOCS-dev/JOJ3/internal/parsers/cppcheck" _ "focs.ji.sjtu.edu.cn/git/FOCS-dev/JOJ3/internal/parsers/cpplint" _ "focs.ji.sjtu.edu.cn/git/FOCS-dev/JOJ3/internal/parsers/diff" _ "focs.ji.sjtu.edu.cn/git/FOCS-dev/JOJ3/internal/parsers/dummy" diff --git a/internal/parsers/cppcheck/meta.go b/internal/parsers/cppcheck/meta.go new file mode 100644 index 0000000..b811ad4 --- /dev/null +++ b/internal/parsers/cppcheck/meta.go @@ -0,0 +1,9 @@ +package cppcheck + +import "focs.ji.sjtu.edu.cn/git/FOCS-dev/JOJ3/internal/stage" + +var name = "cppcheck" + +func init() { + stage.RegisterParser(name, &CppCheck{}) +} diff --git a/internal/parsers/cppcheck/parser.go b/internal/parsers/cppcheck/parser.go new file mode 100644 index 0000000..69f39cc --- /dev/null +++ b/internal/parsers/cppcheck/parser.go @@ -0,0 +1,85 @@ +package cppcheck + +import ( + "encoding/json" + "fmt" + "strings" + + "focs.ji.sjtu.edu.cn/git/FOCS-dev/JOJ3/internal/stage" + "github.com/criyle/go-judge/envexec" +) + +type CppCheck struct{} + +type Match struct { + Severity []string + Score int +} + +type Conf struct { + Score int `default:"100"` + Matches []Match +} + +type Record struct { + File string `json:"file"` + Line int `json:"line"` + Column int `json:"column"` + Severity string `json:"severity"` + Message string `json:"message"` + Id string `json:"id"` +} + +func Parse(executorResult stage.ExecutorResult, conf Conf) stage.ParserResult { + // stdout := executorResult.Files["stdout"] + stderr := executorResult.Files["stderr"] + + if executorResult.Status != stage.Status(envexec.StatusAccepted) { + return stage.ParserResult{ + Score: 0, + Comment: fmt.Sprintf( + "Unexpected executor status: %s.\nStderr: %s", + executorResult.Status, stderr, + ), + } + } + records := make([]Record, 0) + lines := strings.Split(stderr, "\n") + for _, line := range lines { + if strings.TrimSpace(line) == "" { + continue + } + var record Record + _ = json.Unmarshal([]byte(line), &record) + records = append(records, record) + } + comment, score, err := GetResult(records, conf) + if err != nil { + return stage.ParserResult{ + Score: 0, + Comment: fmt.Sprintf( + "Unexpected parser error: %s.", + err, + ), + } + } + + return stage.ParserResult{ + Score: score, + Comment: comment, + } +} + +func (*CppCheck) Run(results []stage.ExecutorResult, confAny any) ( + []stage.ParserResult, bool, error, +) { + conf, err := stage.DecodeConf[Conf](confAny) + if err != nil { + return nil, true, err + } + var res []stage.ParserResult + for _, result := range results { + res = append(res, Parse(result, *conf)) + } + return res, false, nil +} diff --git a/internal/parsers/cppcheck/score.go b/internal/parsers/cppcheck/score.go new file mode 100644 index 0000000..d6028fb --- /dev/null +++ b/internal/parsers/cppcheck/score.go @@ -0,0 +1,66 @@ +package cppcheck + +import "fmt" + +type Severity int + +const ( + ERROR Severity = iota + WARNING + PROBABILITY + PERFORMANCE + STYLE + INFORMATION + UNKNOWN +) + +func severityFromString(severityString string) (Severity, error) { + switch severityString { + case "error": + return ERROR, nil + case "warning": + return WARNING, nil + case "probability": + return PROBABILITY, nil + case "performance": + return PERFORMANCE, nil + case "style": + return STYLE, nil + case "information": + return INFORMATION, nil + default: + return UNKNOWN, fmt.Errorf("unkown severity type \"%s\" for cppcheck", severityString) + } +} + +func GetResult(records []Record, conf Conf) (string, int, error) { + result := "### Test results summary\n\n" + var severityCounts [6]int + var severityScore [6]int + score := conf.Score + + for _, match := range conf.Matches { + severities := match.Severity + score := match.Score + for _, severityString := range severities { + severity, err := severityFromString(severityString) + if err != nil { + return "", 0, err + } + severityScore[int(severity)] = score + } + } + + for _, record := range records { + severity, _ := severityFromString(record.Severity) + severityCounts[int(severity)] += 1 + score -= severityScore[int(severity)] + } + result += fmt.Sprintf("1. error: %d\n", severityCounts[0]) + result += fmt.Sprintf("2. warning: %d\n", severityCounts[1]) + result += fmt.Sprintf("3. probability: %d\n", severityCounts[2]) + result += fmt.Sprintf("4. performance: %d\n", severityCounts[3]) + result += fmt.Sprintf("5. style: %d\n", severityCounts[4]) + result += fmt.Sprintf("6. information: %d", severityCounts[5]) + return result, score, nil +}