From e7df875d245ef964ae199a25511e66fbd27a6837 Mon Sep 17 00:00:00 2001 From: Boming Zhang <bomingzh@sjtu.edu.cn> Date: Sat, 8 Jun 2024 20:43:46 -0400 Subject: [PATCH 1/5] feat: elf parser only parse json output to struct --- internal/parsers/elf/meta.go | 9 ++++ internal/parsers/elf/model.go | 41 +++++++++++++++ internal/parsers/elf/parser.go | 96 ++++++++++++++++++++++++++++++++++ 3 files changed, 146 insertions(+) create mode 100644 internal/parsers/elf/meta.go create mode 100644 internal/parsers/elf/model.go create mode 100644 internal/parsers/elf/parser.go diff --git a/internal/parsers/elf/meta.go b/internal/parsers/elf/meta.go new file mode 100644 index 0000000..0557a3c --- /dev/null +++ b/internal/parsers/elf/meta.go @@ -0,0 +1,9 @@ +package elf + +import "focs.ji.sjtu.edu.cn/git/FOCS-dev/JOJ3/internal/stage" + +var name = "elf" + +func init() { + stage.RegisterParser(name, &Elf{}) +} diff --git a/internal/parsers/elf/model.go b/internal/parsers/elf/model.go new file mode 100644 index 0000000..a8e7960 --- /dev/null +++ b/internal/parsers/elf/model.go @@ -0,0 +1,41 @@ +package elf + +type Toplevel struct { + Title string `json:"title"` + Modules []Module `json:"modules"` +} + +type Module struct { + Entries []Entry `json:"entries"` + DebugInfo string `json:"debug_info"` +} + +type Entry []any + +type Report struct { + File string `json:"file"` + Name string `json:"name"` + Cases []Case `json:"cases" mapstructure:"cases"` +} + +type Case struct { + Binders []Binder `mapstructure:"binders"` + Context *string `mapstructure:"context"` + Depths *int `mapstructure:"depths"` + Code *string `mapstructure:"code"` + Plain *int `mapstructure:"plain"` + Weighed *float64 `mapstructure:"weighed"` + Detail *string `mapstructure:"detail"` + SimilarityRate float64 `mapstructure:"similarity_rate"` + Sources []Source `mapstructure:"srcs"` +} + +type Binder struct { + Binder string `json:"binder"` + Pos string `json:"pos"` +} + +type Source struct { + Context string `json:"context"` + Code string `json:"code"` +} diff --git a/internal/parsers/elf/parser.go b/internal/parsers/elf/parser.go new file mode 100644 index 0000000..96e4abd --- /dev/null +++ b/internal/parsers/elf/parser.go @@ -0,0 +1,96 @@ +package elf + +import ( + "encoding/json" + "fmt" + "log/slog" + + "focs.ji.sjtu.edu.cn/git/FOCS-dev/JOJ3/internal/stage" + "github.com/criyle/go-judge/envexec" + "github.com/mitchellh/mapstructure" +) + +type Conf struct { + Score int + Comment string +} + +type Elf struct{} + +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, + ), + } + } + var topLevel Toplevel + err := json.Unmarshal([]byte(stdout), &topLevel) + if err != nil { + return stage.ParserResult{ + Score: 0, + Comment: fmt.Sprintf("Failed to parse result: %s", err), + } + } + for _, module := range topLevel.Modules { + for _, entry := range module.Entries { + kind := entry[0].(string) + report := Report{} + err := mapstructure.Decode(entry[1], &report) + if err != nil { + slog.Error("elf parse", "mapstructure decode err", err) + } + slog.Debug("elf parse", "report file", report.File) + slog.Debug("elf parse", "report name", report.Name) + slog.Debug("elf parse", "report kind", kind) + for _, caseObj := range report.Cases { + switch kind { + case "ParenDep": + slog.Debug("elf parse", "binders", caseObj.Binders) + slog.Debug("elf parse", "context", caseObj.Context) + slog.Debug("elf parse", "depths", caseObj.Depths) + slog.Debug("elf parse", "code", caseObj.Code) + case "CodeLen": + slog.Debug("elf parse", "binders", caseObj.Binders) + slog.Debug("elf parse", "context", caseObj.Context) + slog.Debug("elf parse", "plain", caseObj.Plain) + slog.Debug("elf parse", "weighed", caseObj.Weighed) + slog.Debug("elf parse", "code", caseObj.Code) + case "OverArity": + slog.Debug("elf parse", "binders", caseObj.Binders) + slog.Debug("elf parse", "context", caseObj.Context) + slog.Debug("elf parse", "detail", caseObj.Detail) + slog.Debug("elf parse", "code", caseObj.Code) + case "CodeDup": + slog.Debug("elf parse", "similarity rate", caseObj.SimilarityRate) + for _, source := range caseObj.Sources { + slog.Debug("elf parse", "context", source.Context, "code", source.Code) + } + } + } + } + } + return stage.ParserResult{ + Score: conf.Score, + Comment: conf.Comment, + } +} + +func (*Elf) 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 +} -- 2.30.2 From 0bfedd462528679b479f0af6d6939f3077ae1df5 Mon Sep 17 00:00:00 2001 From: Boming Zhang <bomingzh@sjtu.edu.cn> Date: Sat, 15 Jun 2024 01:01:57 -0400 Subject: [PATCH 2/5] fix: remove pointer in elf `Case` --- internal/parsers/elf/model.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/internal/parsers/elf/model.go b/internal/parsers/elf/model.go index a8e7960..28806c4 100644 --- a/internal/parsers/elf/model.go +++ b/internal/parsers/elf/model.go @@ -20,12 +20,12 @@ type Report struct { type Case struct { Binders []Binder `mapstructure:"binders"` - Context *string `mapstructure:"context"` - Depths *int `mapstructure:"depths"` - Code *string `mapstructure:"code"` - Plain *int `mapstructure:"plain"` - Weighed *float64 `mapstructure:"weighed"` - Detail *string `mapstructure:"detail"` + Context string `mapstructure:"context"` + Depths int `mapstructure:"depths"` + Code string `mapstructure:"code"` + Plain int `mapstructure:"plain"` + Weighed float64 `mapstructure:"weighed"` + Detail string `mapstructure:"detail"` SimilarityRate float64 `mapstructure:"similarity_rate"` Sources []Source `mapstructure:"srcs"` } -- 2.30.2 From b57e3f81cafd480cd6edb6afedc175914abc863d Mon Sep 17 00:00:00 2001 From: Boming Zhang <bomingzh@sjtu.edu.cn> Date: Fri, 4 Oct 2024 05:20:55 -0400 Subject: [PATCH 3/5] chore: fix module name --- internal/parsers/elf/meta.go | 2 +- internal/parsers/elf/parser.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/parsers/elf/meta.go b/internal/parsers/elf/meta.go index 0557a3c..d952f2b 100644 --- a/internal/parsers/elf/meta.go +++ b/internal/parsers/elf/meta.go @@ -1,6 +1,6 @@ package elf -import "focs.ji.sjtu.edu.cn/git/FOCS-dev/JOJ3/internal/stage" +import "github.com/joint-online-judge/JOJ3/internal/stage" var name = "elf" diff --git a/internal/parsers/elf/parser.go b/internal/parsers/elf/parser.go index 96e4abd..db5477a 100644 --- a/internal/parsers/elf/parser.go +++ b/internal/parsers/elf/parser.go @@ -5,8 +5,8 @@ import ( "fmt" "log/slog" - "focs.ji.sjtu.edu.cn/git/FOCS-dev/JOJ3/internal/stage" "github.com/criyle/go-judge/envexec" + "github.com/joint-online-judge/JOJ3/internal/stage" "github.com/mitchellh/mapstructure" ) -- 2.30.2 From a4232884c5bd8ab3e189f38f287eccf8d7b62295 Mon Sep 17 00:00:00 2001 From: Boming Zhang <bomingzh@sjtu.edu.cn> Date: Sat, 24 May 2025 04:18:16 -0400 Subject: [PATCH 4/5] refactor(parser/elf): modern structure --- internal/{parsers => parser}/elf/meta.go | 7 +++++++ internal/{parsers => parser}/elf/model.go | 0 internal/{parsers => parser}/elf/parser.go | 12 ++---------- 3 files changed, 9 insertions(+), 10 deletions(-) rename internal/{parsers => parser}/elf/meta.go (67%) rename internal/{parsers => parser}/elf/model.go (100%) rename internal/{parsers => parser}/elf/parser.go (92%) diff --git a/internal/parsers/elf/meta.go b/internal/parser/elf/meta.go similarity index 67% rename from internal/parsers/elf/meta.go rename to internal/parser/elf/meta.go index d952f2b..ed52fc7 100644 --- a/internal/parsers/elf/meta.go +++ b/internal/parser/elf/meta.go @@ -4,6 +4,13 @@ import "github.com/joint-online-judge/JOJ3/internal/stage" var name = "elf" +type Conf struct { + Score int + Comment string +} + +type Elf struct{} + func init() { stage.RegisterParser(name, &Elf{}) } diff --git a/internal/parsers/elf/model.go b/internal/parser/elf/model.go similarity index 100% rename from internal/parsers/elf/model.go rename to internal/parser/elf/model.go diff --git a/internal/parsers/elf/parser.go b/internal/parser/elf/parser.go similarity index 92% rename from internal/parsers/elf/parser.go rename to internal/parser/elf/parser.go index db5477a..a876700 100644 --- a/internal/parsers/elf/parser.go +++ b/internal/parser/elf/parser.go @@ -5,22 +5,14 @@ import ( "fmt" "log/slog" - "github.com/criyle/go-judge/envexec" "github.com/joint-online-judge/JOJ3/internal/stage" "github.com/mitchellh/mapstructure" ) -type Conf struct { - Score int - Comment string -} - -type Elf struct{} - func Parse(executorResult stage.ExecutorResult, conf Conf) stage.ParserResult { stdout := executorResult.Files["stdout"] stderr := executorResult.Files["stderr"] - if executorResult.Status != stage.Status(envexec.StatusAccepted) { + if executorResult.Status != stage.StatusAccepted { return stage.ParserResult{ Score: 0, Comment: fmt.Sprintf( @@ -88,7 +80,7 @@ func (*Elf) Run(results []stage.ExecutorResult, confAny any) ( if err != nil { return nil, true, err } - var res []stage.ParserResult + res := make([]stage.ParserResult, 0, len(results)) for _, result := range results { res = append(res, Parse(result, *conf)) } -- 2.30.2 From 3dfdf5a2573836cf151e95b7a1ce46b196ec1f00 Mon Sep 17 00:00:00 2001 From: Boming Zhang <bomingzh@sjtu.edu.cn> Date: Sat, 24 May 2025 04:47:45 -0400 Subject: [PATCH 5/5] feat(parser/elf): score & comment --- internal/parser/elf/meta.go | 15 ++++- internal/parser/elf/model.go | 21 ++++++- internal/parser/elf/parser.go | 112 ++++++++++++++++++++++------------ 3 files changed, 107 insertions(+), 41 deletions(-) diff --git a/internal/parser/elf/meta.go b/internal/parser/elf/meta.go index ed52fc7..8c37314 100644 --- a/internal/parser/elf/meta.go +++ b/internal/parser/elf/meta.go @@ -1,12 +1,23 @@ +// Package elf parses output of the elf static analysis tool to +// assign scores based on detected code issues. + package elf import "github.com/joint-online-judge/JOJ3/internal/stage" var name = "elf" +type Match struct { + Keywords []string + Score int +} + type Conf struct { - Score int - Comment string + Score int + Matches []Match + Stdout string `default:"stdout"` + Stderr string `default:"stderr"` + ForceQuitOnDeduct bool `default:"false"` } type Elf struct{} diff --git a/internal/parser/elf/model.go b/internal/parser/elf/model.go index 28806c4..f998543 100644 --- a/internal/parser/elf/model.go +++ b/internal/parser/elf/model.go @@ -1,5 +1,10 @@ package elf +import ( + "fmt" + "strings" +) + type Toplevel struct { Title string `json:"title"` Modules []Module `json:"modules"` @@ -19,7 +24,7 @@ type Report struct { } type Case struct { - Binders []Binder `mapstructure:"binders"` + Binders Binders `mapstructure:"binders"` Context string `mapstructure:"context"` Depths int `mapstructure:"depths"` Code string `mapstructure:"code"` @@ -35,6 +40,20 @@ type Binder struct { Pos string `json:"pos"` } +func (b Binder) String() string { + return fmt.Sprintf("In the definition of %s (at %s)", b.Binder, b.Pos) +} + +type Binders []Binder + +func (bs Binders) String() string { + s := make([]string, 0, len(bs)) + for _, b := range bs { + s = append(s, b.String()) + } + return strings.Join(s, "; ") +} + type Source struct { Context string `json:"context"` Code string `json:"code"` diff --git a/internal/parser/elf/parser.go b/internal/parser/elf/parser.go index a876700..2d2eaea 100644 --- a/internal/parser/elf/parser.go +++ b/internal/parser/elf/parser.go @@ -4,31 +4,28 @@ import ( "encoding/json" "fmt" "log/slog" + "strings" "github.com/joint-online-judge/JOJ3/internal/stage" "github.com/mitchellh/mapstructure" ) -func Parse(executorResult stage.ExecutorResult, conf Conf) stage.ParserResult { - stdout := executorResult.Files["stdout"] - stderr := executorResult.Files["stderr"] - if executorResult.Status != stage.StatusAccepted { - return stage.ParserResult{ - Score: 0, - Comment: fmt.Sprintf( - "Unexpected executor status: %s.\nStderr: %s", - executorResult.Status, stderr, - ), - } - } +func (p *Elf) parse(executorResult stage.ExecutorResult, conf Conf) stage.ParserResult { + stdout := executorResult.Files[conf.Stdout] + // stderr := executorResult.Files[conf.Stderr] var topLevel Toplevel err := json.Unmarshal([]byte(stdout), &topLevel) if err != nil { return stage.ParserResult{ - Score: 0, - Comment: fmt.Sprintf("Failed to parse result: %s", err), + Score: 0, + Comment: fmt.Sprintf( + "Unexpected parser error: %s.", + err, + ), } } + score := conf.Score + comment := "" for _, module := range topLevel.Modules { for _, entry := range module.Entries { kind := entry[0].(string) @@ -37,43 +34,77 @@ func Parse(executorResult stage.ExecutorResult, conf Conf) stage.ParserResult { if err != nil { slog.Error("elf parse", "mapstructure decode err", err) } - slog.Debug("elf parse", "report file", report.File) - slog.Debug("elf parse", "report name", report.Name) - slog.Debug("elf parse", "report kind", kind) + comment += fmt.Sprintf("### [%s] %s\n", report.File, report.Name) for _, caseObj := range report.Cases { + for _, match := range conf.Matches { + for _, keyword := range match.Keywords { + if strings.Contains(kind, keyword) { + score += -match.Score + } + } + } switch kind { case "ParenDep": - slog.Debug("elf parse", "binders", caseObj.Binders) - slog.Debug("elf parse", "context", caseObj.Context) - slog.Debug("elf parse", "depths", caseObj.Depths) - slog.Debug("elf parse", "code", caseObj.Code) + // "<binders>:\n<context> below reaches a parentheses depths of <depths>:\n<code>" + comment += fmt.Sprintf( + "%s:\n%s below reaches a parentheses depths of %d:\n"+ + "```%s\n```\n", + caseObj.Binders, + caseObj.Context, + caseObj.Depths, + caseObj.Code, + ) case "CodeLen": - slog.Debug("elf parse", "binders", caseObj.Binders) - slog.Debug("elf parse", "context", caseObj.Context) - slog.Debug("elf parse", "plain", caseObj.Plain) - slog.Debug("elf parse", "weighed", caseObj.Weighed) - slog.Debug("elf parse", "code", caseObj.Code) + // "<binders>:\n<context> below excceeds a code length upper bound with <plain> (weighed: <weighed>):\n<code>" + comment += fmt.Sprintf( + "%s:\n%s below excceeds a code length "+ + "upper bound with %d (weighed: %f):\n"+ + "```%s\n```\n", + caseObj.Binders, + caseObj.Context, + caseObj.Plain, + caseObj.Weighed, + caseObj.Code, + ) case "OverArity": - slog.Debug("elf parse", "binders", caseObj.Binders) - slog.Debug("elf parse", "context", caseObj.Context) - slog.Debug("elf parse", "detail", caseObj.Detail) - slog.Debug("elf parse", "code", caseObj.Code) + // "<binders>:\n<context> below hits <detail>:\n<code>" + comment += fmt.Sprintf( + "%s:\n%s below hits %s:\n```%s\n```\n", + caseObj.Binders, + caseObj.Context, + caseObj.Detail, + caseObj.Code, + ) case "CodeDup": - slog.Debug("elf parse", "similarity rate", caseObj.SimilarityRate) - for _, source := range caseObj.Sources { - slog.Debug("elf parse", "context", source.Context, "code", source.Code) + if len(caseObj.Sources) != 2 { + slog.Error("elf parse", "code dup sources length", len(caseObj.Sources)) } + context0 := caseObj.Sources[0].Context + code0 := caseObj.Sources[0].Code + context1 := caseObj.Sources[1].Context + code1 := caseObj.Sources[1].Code + // "The code below has a similarity rate of <similarity_rate>:\n- <context1>:\n\t<code1>\n- <context2>:\n\t<code2>" + comment += fmt.Sprintf( + "The code below has a similarity rate of %f:\n"+ + "- %s:\n```%s\n```\n"+ + "- %s:\n```%s\n```\n", + caseObj.SimilarityRate, + context0, + code0, + context1, + code1, + ) } } } } return stage.ParserResult{ - Score: conf.Score, - Comment: conf.Comment, + Score: score, + Comment: comment, } } -func (*Elf) Run(results []stage.ExecutorResult, confAny any) ( +func (p *Elf) Run(results []stage.ExecutorResult, confAny any) ( []stage.ParserResult, bool, error, ) { conf, err := stage.DecodeConf[Conf](confAny) @@ -81,8 +112,13 @@ func (*Elf) Run(results []stage.ExecutorResult, confAny any) ( return nil, true, err } res := make([]stage.ParserResult, 0, len(results)) + forceQuit := false for _, result := range results { - res = append(res, Parse(result, *conf)) + parseRes := p.parse(result, *conf) + if conf.ForceQuitOnDeduct && parseRes.Score < conf.Score { + forceQuit = true + } + res = append(res, parseRes) } - return res, false, nil + return res, forceQuit, nil } -- 2.30.2