From f357fd2756e80e76042a205b964ae990942eefb4 Mon Sep 17 00:00:00 2001 From: Boming Zhang Date: Sat, 24 May 2025 17:00:21 +0800 Subject: [PATCH] feat: elf parser (#21) (#30) Reviewed-on: https://focs.ji.sjtu.edu.cn/git/JOJ/JOJ3/pulls/30 Co-authored-by: Boming Zhang Co-committed-by: Boming Zhang --- internal/parser/elf/meta.go | 27 ++++++++ internal/parser/elf/model.go | 60 ++++++++++++++++ internal/parser/elf/parser.go | 124 ++++++++++++++++++++++++++++++++++ 3 files changed, 211 insertions(+) create mode 100644 internal/parser/elf/meta.go create mode 100644 internal/parser/elf/model.go create mode 100644 internal/parser/elf/parser.go diff --git a/internal/parser/elf/meta.go b/internal/parser/elf/meta.go new file mode 100644 index 0000000..8c37314 --- /dev/null +++ b/internal/parser/elf/meta.go @@ -0,0 +1,27 @@ +// 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 + Matches []Match + Stdout string `default:"stdout"` + Stderr string `default:"stderr"` + ForceQuitOnDeduct bool `default:"false"` +} + +type Elf struct{} + +func init() { + stage.RegisterParser(name, &Elf{}) +} diff --git a/internal/parser/elf/model.go b/internal/parser/elf/model.go new file mode 100644 index 0000000..f998543 --- /dev/null +++ b/internal/parser/elf/model.go @@ -0,0 +1,60 @@ +package elf + +import ( + "fmt" + "strings" +) + +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 Binders `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"` +} + +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 new file mode 100644 index 0000000..2d2eaea --- /dev/null +++ b/internal/parser/elf/parser.go @@ -0,0 +1,124 @@ +package elf + +import ( + "encoding/json" + "fmt" + "log/slog" + "strings" + + "github.com/joint-online-judge/JOJ3/internal/stage" + "github.com/mitchellh/mapstructure" +) + +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( + "Unexpected parser error: %s.", + err, + ), + } + } + score := conf.Score + comment := "" + 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) + } + 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": + // ":\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": + // ":\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": + // ":\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": + 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: score, + Comment: comment, + } +} + +func (p *Elf) Run(results []stage.ExecutorResult, confAny any) ( + []stage.ParserResult, bool, error, +) { + conf, err := stage.DecodeConf[Conf](confAny) + if err != nil { + return nil, true, err + } + res := make([]stage.ParserResult, 0, len(results)) + forceQuit := false + for _, result := range results { + parseRes := p.parse(result, *conf) + if conf.ForceQuitOnDeduct && parseRes.Score < conf.Score { + forceQuit = true + } + res = append(res, parseRes) + } + return res, forceQuit, nil +}