feat(parser/elf): score & comment
This commit is contained in:
parent
a4232884c5
commit
3dfdf5a257
|
@ -1,12 +1,23 @@
|
|||
// 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
|
||||
Comment string
|
||||
Score int
|
||||
Matches []Match
|
||||
Stdout string `default:"stdout"`
|
||||
Stderr string `default:"stderr"`
|
||||
ForceQuitOnDeduct bool `default:"false"`
|
||||
}
|
||||
|
||||
type Elf struct{}
|
||||
|
|
|
@ -1,5 +1,10 @@
|
|||
package elf
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Toplevel struct {
|
||||
Title string `json:"title"`
|
||||
Modules []Module `json:"modules"`
|
||||
|
@ -19,7 +24,7 @@ type Report struct {
|
|||
}
|
||||
|
||||
type Case struct {
|
||||
Binders []Binder `mapstructure:"binders"`
|
||||
Binders Binders `mapstructure:"binders"`
|
||||
Context string `mapstructure:"context"`
|
||||
Depths int `mapstructure:"depths"`
|
||||
Code string `mapstructure:"code"`
|
||||
|
@ -35,6 +40,20 @@ type Binder struct {
|
|||
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"`
|
||||
|
|
|
@ -4,31 +4,28 @@ import (
|
|||
"encoding/json"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"strings"
|
||||
|
||||
"github.com/joint-online-judge/JOJ3/internal/stage"
|
||||
"github.com/mitchellh/mapstructure"
|
||||
)
|
||||
|
||||
func Parse(executorResult stage.ExecutorResult, conf Conf) stage.ParserResult {
|
||||
stdout := executorResult.Files["stdout"]
|
||||
stderr := executorResult.Files["stderr"]
|
||||
if executorResult.Status != stage.StatusAccepted {
|
||||
return stage.ParserResult{
|
||||
Score: 0,
|
||||
Comment: fmt.Sprintf(
|
||||
"Unexpected executor status: %s.\nStderr: %s",
|
||||
executorResult.Status, stderr,
|
||||
),
|
||||
}
|
||||
}
|
||||
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("Failed to parse result: %s", err),
|
||||
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)
|
||||
|
@ -37,43 +34,77 @@ func Parse(executorResult stage.ExecutorResult, conf Conf) stage.ParserResult {
|
|||
if err != nil {
|
||||
slog.Error("elf parse", "mapstructure decode err", err)
|
||||
}
|
||||
slog.Debug("elf parse", "report file", report.File)
|
||||
slog.Debug("elf parse", "report name", report.Name)
|
||||
slog.Debug("elf parse", "report kind", kind)
|
||||
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":
|
||||
slog.Debug("elf parse", "binders", caseObj.Binders)
|
||||
slog.Debug("elf parse", "context", caseObj.Context)
|
||||
slog.Debug("elf parse", "depths", caseObj.Depths)
|
||||
slog.Debug("elf parse", "code", caseObj.Code)
|
||||
// "<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":
|
||||
slog.Debug("elf parse", "binders", caseObj.Binders)
|
||||
slog.Debug("elf parse", "context", caseObj.Context)
|
||||
slog.Debug("elf parse", "plain", caseObj.Plain)
|
||||
slog.Debug("elf parse", "weighed", caseObj.Weighed)
|
||||
slog.Debug("elf parse", "code", caseObj.Code)
|
||||
// "<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":
|
||||
slog.Debug("elf parse", "binders", caseObj.Binders)
|
||||
slog.Debug("elf parse", "context", caseObj.Context)
|
||||
slog.Debug("elf parse", "detail", caseObj.Detail)
|
||||
slog.Debug("elf parse", "code", caseObj.Code)
|
||||
// "<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":
|
||||
slog.Debug("elf parse", "similarity rate", caseObj.SimilarityRate)
|
||||
for _, source := range caseObj.Sources {
|
||||
slog.Debug("elf parse", "context", source.Context, "code", source.Code)
|
||||
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: conf.Score,
|
||||
Comment: conf.Comment,
|
||||
Score: score,
|
||||
Comment: comment,
|
||||
}
|
||||
}
|
||||
|
||||
func (*Elf) Run(results []stage.ExecutorResult, confAny any) (
|
||||
func (p *Elf) Run(results []stage.ExecutorResult, confAny any) (
|
||||
[]stage.ParserResult, bool, error,
|
||||
) {
|
||||
conf, err := stage.DecodeConf[Conf](confAny)
|
||||
|
@ -81,8 +112,13 @@ func (*Elf) Run(results []stage.ExecutorResult, confAny any) (
|
|||
return nil, true, err
|
||||
}
|
||||
res := make([]stage.ParserResult, 0, len(results))
|
||||
forceQuit := false
|
||||
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
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user