feat: elf parser (#21) (#30)
All checks were successful
submodules sync / sync (push) Successful in 59s
build / build (push) Successful in 2m50s
build / trigger-build-image (push) Successful in 13s

Reviewed-on: #30
Co-authored-by: Boming Zhang <bomingzh@sjtu.edu.cn>
Co-committed-by: Boming Zhang <bomingzh@sjtu.edu.cn>
This commit is contained in:
张泊明518370910136 2025-05-24 17:00:21 +08:00 committed by 张泊明518370910136
parent 5dec3b1bda
commit f357fd2756
3 changed files with 211 additions and 0 deletions

View File

@ -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{})
}

View File

@ -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"`
}

View File

@ -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":
// "<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":
// "<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":
// "<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":
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: 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
}