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 | 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 | ||||||
|  | 		} | ||||||
|  | 		res = append(res, parseRes) | ||||||
| 	} | 	} | ||||||
| 	return res, false, nil | 	return res, forceQuit, nil | ||||||
| } | } | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue
	
	Block a user