feat(parser/elf): score & comment
All checks were successful
build / build (pull_request) Successful in 6m40s
build / build (push) Successful in 6m42s
build / trigger-build-image (push) Has been skipped
build / trigger-build-image (pull_request) Has been skipped

This commit is contained in:
张泊明518370910136 2025-05-24 04:47:45 -04:00
parent a4232884c5
commit 3dfdf5a257
GPG Key ID: D47306D7062CDA9D
3 changed files with 107 additions and 41 deletions

View File

@ -1,12 +1,23 @@
// Package elf parses output of the elf static analysis tool to
// assign scores based on detected code issues.
package elf package elf
import "github.com/joint-online-judge/JOJ3/internal/stage" import "github.com/joint-online-judge/JOJ3/internal/stage"
var name = "elf" var name = "elf"
type Match struct {
Keywords []string
Score int
}
type Conf struct { type Conf struct {
Score int Score int
Comment string Matches []Match
Stdout string `default:"stdout"`
Stderr string `default:"stderr"`
ForceQuitOnDeduct bool `default:"false"`
} }
type Elf struct{} type Elf struct{}

View File

@ -1,5 +1,10 @@
package elf package elf
import (
"fmt"
"strings"
)
type Toplevel struct { type Toplevel struct {
Title string `json:"title"` Title string `json:"title"`
Modules []Module `json:"modules"` Modules []Module `json:"modules"`
@ -19,7 +24,7 @@ type Report struct {
} }
type Case struct { type Case struct {
Binders []Binder `mapstructure:"binders"` Binders Binders `mapstructure:"binders"`
Context string `mapstructure:"context"` Context string `mapstructure:"context"`
Depths int `mapstructure:"depths"` Depths int `mapstructure:"depths"`
Code string `mapstructure:"code"` Code string `mapstructure:"code"`
@ -35,6 +40,20 @@ type Binder struct {
Pos string `json:"pos"` 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 { type Source struct {
Context string `json:"context"` Context string `json:"context"`
Code string `json:"code"` Code string `json:"code"`

View File

@ -4,31 +4,28 @@ import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"log/slog" "log/slog"
"strings"
"github.com/joint-online-judge/JOJ3/internal/stage" "github.com/joint-online-judge/JOJ3/internal/stage"
"github.com/mitchellh/mapstructure" "github.com/mitchellh/mapstructure"
) )
func Parse(executorResult stage.ExecutorResult, conf Conf) stage.ParserResult { func (p *Elf) parse(executorResult stage.ExecutorResult, conf Conf) stage.ParserResult {
stdout := executorResult.Files["stdout"] stdout := executorResult.Files[conf.Stdout]
stderr := executorResult.Files["stderr"] // stderr := executorResult.Files[conf.Stderr]
if executorResult.Status != stage.StatusAccepted {
return stage.ParserResult{
Score: 0,
Comment: fmt.Sprintf(
"Unexpected executor status: %s.\nStderr: %s",
executorResult.Status, stderr,
),
}
}
var topLevel Toplevel var topLevel Toplevel
err := json.Unmarshal([]byte(stdout), &topLevel) err := json.Unmarshal([]byte(stdout), &topLevel)
if err != nil { if err != nil {
return stage.ParserResult{ return stage.ParserResult{
Score: 0, Score: 0,
Comment: fmt.Sprintf("Failed to parse result: %s", err), Comment: fmt.Sprintf(
"Unexpected parser error: %s.",
err,
),
} }
} }
score := conf.Score
comment := ""
for _, module := range topLevel.Modules { for _, module := range topLevel.Modules {
for _, entry := range module.Entries { for _, entry := range module.Entries {
kind := entry[0].(string) kind := entry[0].(string)
@ -37,43 +34,77 @@ func Parse(executorResult stage.ExecutorResult, conf Conf) stage.ParserResult {
if err != nil { if err != nil {
slog.Error("elf parse", "mapstructure decode err", err) slog.Error("elf parse", "mapstructure decode err", err)
} }
slog.Debug("elf parse", "report file", report.File) comment += fmt.Sprintf("### [%s] %s\n", report.File, report.Name)
slog.Debug("elf parse", "report name", report.Name)
slog.Debug("elf parse", "report kind", kind)
for _, caseObj := range report.Cases { 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 { switch kind {
case "ParenDep": case "ParenDep":
slog.Debug("elf parse", "binders", caseObj.Binders) // "<binders>:\n<context> below reaches a parentheses depths of <depths>:\n<code>"
slog.Debug("elf parse", "context", caseObj.Context) comment += fmt.Sprintf(
slog.Debug("elf parse", "depths", caseObj.Depths) "%s:\n%s below reaches a parentheses depths of %d:\n"+
slog.Debug("elf parse", "code", caseObj.Code) "```%s\n```\n",
caseObj.Binders,
caseObj.Context,
caseObj.Depths,
caseObj.Code,
)
case "CodeLen": case "CodeLen":
slog.Debug("elf parse", "binders", caseObj.Binders) // "<binders>:\n<context> below excceeds a code length upper bound with <plain> (weighed: <weighed>):\n<code>"
slog.Debug("elf parse", "context", caseObj.Context) comment += fmt.Sprintf(
slog.Debug("elf parse", "plain", caseObj.Plain) "%s:\n%s below excceeds a code length "+
slog.Debug("elf parse", "weighed", caseObj.Weighed) "upper bound with %d (weighed: %f):\n"+
slog.Debug("elf parse", "code", caseObj.Code) "```%s\n```\n",
caseObj.Binders,
caseObj.Context,
caseObj.Plain,
caseObj.Weighed,
caseObj.Code,
)
case "OverArity": case "OverArity":
slog.Debug("elf parse", "binders", caseObj.Binders) // "<binders>:\n<context> below hits <detail>:\n<code>"
slog.Debug("elf parse", "context", caseObj.Context) comment += fmt.Sprintf(
slog.Debug("elf parse", "detail", caseObj.Detail) "%s:\n%s below hits %s:\n```%s\n```\n",
slog.Debug("elf parse", "code", caseObj.Code) caseObj.Binders,
caseObj.Context,
caseObj.Detail,
caseObj.Code,
)
case "CodeDup": case "CodeDup":
slog.Debug("elf parse", "similarity rate", caseObj.SimilarityRate) if len(caseObj.Sources) != 2 {
for _, source := range caseObj.Sources { slog.Error("elf parse", "code dup sources length", len(caseObj.Sources))
slog.Debug("elf parse", "context", source.Context, "code", source.Code)
} }
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{ return stage.ParserResult{
Score: conf.Score, Score: score,
Comment: conf.Comment, Comment: comment,
} }
} }
func (*Elf) Run(results []stage.ExecutorResult, confAny any) ( func (p *Elf) Run(results []stage.ExecutorResult, confAny any) (
[]stage.ParserResult, bool, error, []stage.ParserResult, bool, error,
) { ) {
conf, err := stage.DecodeConf[Conf](confAny) conf, err := stage.DecodeConf[Conf](confAny)
@ -81,8 +112,13 @@ func (*Elf) Run(results []stage.ExecutorResult, confAny any) (
return nil, true, err return nil, true, err
} }
res := make([]stage.ParserResult, 0, len(results)) res := make([]stage.ParserResult, 0, len(results))
forceQuit := false
for _, result := range results { 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
} }