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