From 3dfdf5a2573836cf151e95b7a1ce46b196ec1f00 Mon Sep 17 00:00:00 2001 From: Boming Zhang Date: Sat, 24 May 2025 04:47:45 -0400 Subject: [PATCH] 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) + // ":\n below reaches a parentheses depths of :\n" + 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) + // ":\n below excceeds a code length upper bound with (weighed: ):\n" + 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) + // ":\n below hits :\n" + 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 :\n- :\n\t\n- :\n\t" + 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 }