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:
parent
5dec3b1bda
commit
f357fd2756
27
internal/parser/elf/meta.go
Normal file
27
internal/parser/elf/meta.go
Normal 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{})
|
||||
}
|
60
internal/parser/elf/model.go
Normal file
60
internal/parser/elf/model.go
Normal 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"`
|
||||
}
|
124
internal/parser/elf/parser.go
Normal file
124
internal/parser/elf/parser.go
Normal 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
|
||||
}
|
Loading…
Reference in New Issue
Block a user