feat: elf parser (#21) #30
| 
						 | 
					@ -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{}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -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"`
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -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
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	return res, false, nil
 | 
							res = append(res, parseRes)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return res, forceQuit, nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in New Issue
	
	Block a user