diff --git a/errgoengine.go b/errgoengine.go index dde75da..25dae78 100644 --- a/errgoengine.go +++ b/errgoengine.go @@ -107,7 +107,9 @@ func (e *ErrgoEngine) Analyze(workingPath, msg string) (*CompiledErrorTemplate, for _, node := range contextData.TraceStack { contents, err := e.FS.ReadFile(node.DocumentPath) if err != nil { - return nil, nil, err + // return nil, nil, err + // Do not return error if file not found + continue } // Skip stub files diff --git a/error_templates/java/java.go b/error_templates/java/java.go index 0ed35bd..75714d0 100644 --- a/error_templates/java/java.go +++ b/error_templates/java/java.go @@ -14,6 +14,7 @@ func LoadErrorTemplates(errorTemplates *lib.ErrorTemplates) { errorTemplates.MustAdd(java.Language, ArrayIndexOutOfBoundsException) errorTemplates.MustAdd(java.Language, ArithmeticException) errorTemplates.MustAdd(java.Language, NegativeArraySizeException) + errorTemplates.MustAdd(java.Language, StringIndexOutOfBoundsException) // Compile time errorTemplates.MustAdd(java.Language, PublicClassFilenameMismatchError) @@ -119,3 +120,30 @@ func castValueNode(node lib.SyntaxNode, targetSym lib.Symbol) string { return node.Text() } } + +func wrapWithIfStatement(s *lib.BugFixStep, d *lib.Document, cond string, loc lib.Location) { + spaces := d.LineAt(loc.StartPos.Line)[:loc.StartPos.Column] + if len(spaces) == 0 { + // do not create if statement if it is inside an expression + return + } + + indent := spaces + if len(indent) > 4 { + indent = spaces[:4] + } + + s.AddFix(lib.FixSuggestion{ + NewText: spaces + fmt.Sprintf("if (%s) {\n", cond) + indent, + StartPosition: lib.Position{ + Line: loc.StartPos.Line, + }, + EndPosition: lib.Position{ + Line: loc.StartPos.Line, + }, + }).AddFix(lib.FixSuggestion{ + NewText: "\n" + spaces + "}", + StartPosition: loc.EndPos, + EndPosition: loc.EndPos, + }) +} diff --git a/error_templates/java/string_index_out_of_bounds_exception.go b/error_templates/java/string_index_out_of_bounds_exception.go new file mode 100644 index 0000000..015c8ab --- /dev/null +++ b/error_templates/java/string_index_out_of_bounds_exception.go @@ -0,0 +1,67 @@ +package java + +import ( + "fmt" + "strconv" + + lib "github.com/nedpals/errgoengine" +) + +type stringIndexOutOfBoundsExceptionCtx struct { + parentNode lib.SyntaxNode + grandParentNode lib.SyntaxNode +} + +var StringIndexOutOfBoundsException = lib.ErrorTemplate{ + Name: "StringIndexOutOfBoundsException", + Pattern: runtimeErrorPattern("java.lang.StringIndexOutOfBoundsException", `String index out of range: (?P\d+)`), + OnAnalyzeErrorFn: func(cd *lib.ContextData, m *lib.MainError) { + ctx := stringIndexOutOfBoundsExceptionCtx{} + + for q := m.Nearest.Query(`(method_invocation name: (identifier) @name arguments: (argument_list (_) @arg) (#eq? @name "charAt") (#any-eq? @arg "%s"))`, cd.Variables["index"]); q.Next(); { + if q.CurrentTagName() != "arg" { + continue + } + node := q.CurrentNode() + m.Nearest = node + ctx.parentNode = node.Parent().Parent() // expr -> argument_list -> method_invocation + + // get grandparent node + ctx.grandParentNode = ctx.parentNode.Parent() + if !ctx.grandParentNode.IsNull() { + if ctx.grandParentNode.Type() == "expression_statement" || ctx.grandParentNode.Type() == "variable_declarator" { + ctx.grandParentNode = ctx.grandParentNode.Parent() + } + } + break + } + + m.Context = ctx + }, + OnGenExplainFn: func(cd *lib.ContextData, gen *lib.ExplainGenerator) { + gen.Add("This error occurs because the code is trying to access index %s that is beyond the bounds of the array which only has %s items.", cd.Variables["index"], cd.Variables["length"]) + }, + OnGenBugFixFn: func(cd *lib.ContextData, gen *lib.BugFixGenerator) { + ctx := cd.MainError.Context.(stringIndexOutOfBoundsExceptionCtx) + // arrayLen, _ := strconv.Atoi(cd.Variables["length"]) + index, _ := strconv.Atoi(cd.Variables["index"]) + // symbolTree := cd.Store.InitOrGetSymbolTree(cd.MainError.DocumentPath()) + + // TODO: add a suggestion to add an if statement if the array length is 0 + + gen.Add("Ensure the index is within the string length", func(s *lib.BugFixSuggestion) { + obj := ctx.parentNode.ChildByFieldName("object") + step := s.AddStep("Check that the index used for accessing the character is within the valid range of the string length.") + + wrapWithIfStatement( + step, + cd.MainError.Document, + fmt.Sprintf("%d < %s.length()", index, obj.Text()), + lib.Location{ + StartPos: ctx.grandParentNode.StartPosition(), + EndPos: ctx.grandParentNode.EndPosition(), + }, + ) + }) + }, +} diff --git a/error_templates/java/test_files/string_index_out_of_bounds_error/Main.java b/error_templates/java/test_files/string_index_out_of_bounds_error/Main.java new file mode 100644 index 0000000..ef97577 --- /dev/null +++ b/error_templates/java/test_files/string_index_out_of_bounds_error/Main.java @@ -0,0 +1,8 @@ +public class Main { + public static void main(String[] args) { + String text = "Hello, World!"; + // Attempting to access an index beyond the string's length + char character = text.charAt(20); + System.out.println(character); + } +} diff --git a/error_templates/java/test_files/string_index_out_of_bounds_error/test.txt b/error_templates/java/test_files/string_index_out_of_bounds_error/test.txt new file mode 100644 index 0000000..824e61f --- /dev/null +++ b/error_templates/java/test_files/string_index_out_of_bounds_error/test.txt @@ -0,0 +1,29 @@ +template: "Java.StringIndexOutOfBoundsException" +--- +Exception in thread "main" java.lang.StringIndexOutOfBoundsException: String index out of range: 20 + at java.base/java.lang.StringLatin1.charAt(StringLatin1.java:47) + at java.base/java.lang.String.charAt(String.java:693) + at Main.main(Main.java:5) +=== +template: "Java.StringIndexOutOfBoundsException" +--- +# StringIndexOutOfBoundsException +This error occurs when attempting to access an index beyond the length of a string. +``` + // Attempting to access an index beyond the string's length + char character = text.charAt(20); + ^^ + System.out.println(character); + } +``` +## Steps to fix +### 1. Ensure the index is within the string length +1. Check that the index used for accessing the character is within the valid range of the string length. +```diff ++ if (index < text.length()) { + char character = text.charAt(20); + System.out.println(character); ++ } else { ++ System.out.println("Index out of range."); ++ } +``` diff --git a/language.go b/language.go index 11efd80..14e86b9 100644 --- a/language.go +++ b/language.go @@ -2,7 +2,6 @@ package errgoengine import ( "context" - "encoding/json" "fmt" "io/fs" "regexp" @@ -64,52 +63,52 @@ func (lang *Language) Compile() { panic(fmt.Sprintf("[Language -> %s] AnalyzerFactory must not be nil", lang.Name)) } - if err := lang.compileExternSymbols(); err != nil { - panic(err) - } + // if err := lang.compileExternSymbols(); err != nil { + // panic(err) + // } lang.isCompiled = true } -func (lang *Language) compileExternSymbols() error { - if lang.isCompiled || lang.ExternFS == nil { - return nil - } - - lang.externSymbols = make(map[string]*SymbolTree) - - matches, err := fs.Glob(lang.ExternFS, "**/*.json") - if err != nil { - return err - } - - for _, match := range matches { - if err := lang.compileExternSymbol(match); err != nil { - return err - } - } -} - -func (lang *Language) compileExternSymbol(path string) error { - if lang.isCompiled || lang.ExternFS == nil { - return nil - } - - file, err := lang.ExternFS.Open(path) - if err != nil { - return err - } - - defer file.Close() - - var symTree SymbolTree - if err := json.NewDecoder(file).Decode(&symTree); err != nil { - return err - } - - lang.externSymbols[path] = &symTree - return nil -} +// func (lang *Language) compileExternSymbols() error { +// if lang.isCompiled || lang.ExternFS == nil { +// return nil +// } + +// lang.externSymbols = make(map[string]*SymbolTree) + +// matches, err := fs.Glob(lang.ExternFS, "**/*.json") +// if err != nil { +// return err +// } + +// for _, match := range matches { +// if err := lang.compileExternSymbol(match); err != nil { +// return err +// } +// } +// } + +// func (lang *Language) compileExternSymbol(path string) error { +// if lang.isCompiled || lang.ExternFS == nil { +// return nil +// } + +// file, err := lang.ExternFS.Open(path) +// if err != nil { +// return err +// } + +// defer file.Close() + +// var symTree SymbolTree +// if err := json.NewDecoder(file).Decode(&symTree); err != nil { +// return err +// } + +// lang.externSymbols[path] = &symTree +// return nil +// } // SetTemplateStackTraceRegex sets the language's regex pattern directly. for testing purposes only func SetTemplateStackTraceRegex(lang *Language, pattern *regexp.Regexp) {