feat: elf parser (#21) #30
							
								
								
									
										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