Skip to content

Commit 0e4a013

Browse files
committed
feat: support for Java.NullPointerException
1 parent c7dad23 commit 0e4a013

File tree

3 files changed

+119
-66
lines changed

3 files changed

+119
-66
lines changed

Diff for: error_templates/java/java.go

+2
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,8 @@ func getDefaultValueForType(sym lib.Symbol) string {
7878
return "0"
7979
case java.BuiltinTypes.FloatingPoint.DoubleSymbol:
8080
return "0.0"
81+
case java.BuiltinTypes.StringSymbol:
82+
return "\"example\""
8183
default:
8284
return "null"
8385
}

Diff for: error_templates/java/null_pointer_exception.go

+102-57
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
package java
22

33
import (
4-
"context"
4+
"fmt"
5+
"strings"
56

67
lib "github.com/nedpals/errgoengine"
78
"github.com/nedpals/errgoengine/languages/java"
@@ -10,86 +11,92 @@ import (
1011
type exceptionLocationKind int
1112

1213
const (
13-
fromUnknown exceptionLocationKind = 0
14-
fromFunctionArgument exceptionLocationKind = iota
15-
fromSystemOut exceptionLocationKind = iota
16-
fromArrayAccess exceptionLocationKind = iota
17-
fromExpression exceptionLocationKind = iota
18-
fromMethodInvocation exceptionLocationKind = iota
14+
fromUnknown exceptionLocationKind = iota
15+
fromFunctionArgument
16+
fromSystemOut
17+
fromArrayAccess
18+
fromExpression
19+
fromMethodInvocation
20+
fromAssignment
1921
)
2022

2123
type nullPointerExceptionCtx struct {
2224
kind exceptionLocationKind
2325
// symbolInvolved lib.SyntaxNode
2426
methodName string
2527
origin string
28+
parent lib.SyntaxNode
2629
}
2730

2831
// TODO: unit testing
2932
var NullPointerException = lib.ErrorTemplate{
3033
Name: "NullPointerException",
3134
Pattern: runtimeErrorPattern("java.lang.NullPointerException", ""),
32-
OnAnalyzeErrorFn: func(cd *lib.ContextData, err *lib.MainError) {
35+
OnAnalyzeErrorFn: func(cd *lib.ContextData, m *lib.MainError) {
3336
ctx := nullPointerExceptionCtx{}
3437

35-
// if the offending line is an offending method call, get the argument that triggered the null error
36-
if cd.MainError.Nearest.Type() == "expression_statement" {
37-
exprNode := cd.MainError.Nearest.NamedChild(0)
38+
// NOTE: i hope we will be able to parse the entire
39+
// java system library without hardcoding the definitions lol
40+
for q := m.Nearest.Query(`([
41+
(field_access object: (_) @ident field: (identifier))
42+
(method_invocation object: [
43+
(identifier) @ident
44+
(field_access object: (_) @obj field: (identifier)) @ident
45+
]) @call
46+
(method_invocation arguments: (argument_list (identifier) @ident))
47+
(array_access) @access
48+
]
49+
(#any-match? @obj "^[a-z0-9_]+")
50+
(#not-match? @ident "^[A-Z][a-z0-9_]$"))`); q.Next(); {
51+
tagName := q.CurrentTagName()
52+
node := q.CurrentNode()
3853

39-
// NOTE: i hope we will be able to parse the entire
40-
// java system library without hardcoding the definitions lol
41-
if exprNode.Type() == "method_invocation" {
42-
objNode := exprNode.ChildByFieldName("object")
43-
// check if this is just simple printing
44-
if objNode.Text() == "System.out" {
54+
if tagName == "call" {
55+
if strings.HasPrefix(node.Text(), "System.out.") {
4556
ctx.kind = fromSystemOut
46-
} else if retType := cd.Analyzer.AnalyzeNode(context.Background(), exprNode); retType == java.BuiltinTypes.NullSymbol {
47-
cd.MainError.Nearest = exprNode
48-
ctx.kind = fromMethodInvocation
57+
} else {
58+
// use the next node tagged with "ident"
59+
continue
60+
}
61+
} else if tagName == "access" {
62+
cd.MainError.Nearest = node
63+
ctx.kind = fromArrayAccess
64+
} else if tagName == "ident" {
65+
retType := lib.UnwrapActualReturnType(cd.FindSymbol(node.Text(), node.StartPosition().Index))
66+
if retType != java.BuiltinTypes.NullSymbol {
67+
continue
4968
}
5069

51-
if objNode.Type() == "array_access" {
52-
// inArray = true
53-
cd.MainError.Nearest = exprNode
54-
ctx.kind = fromArrayAccess
55-
} else {
56-
arguments := exprNode.ChildByFieldName("arguments")
57-
for i := 0; i < int(arguments.NamedChildCount()); i++ {
58-
argNode := arguments.NamedChild(i)
59-
retType := cd.Analyzer.AnalyzeNode(context.Background(), argNode)
60-
61-
if retType == java.BuiltinTypes.NullSymbol || argNode.Type() == "array_access" {
62-
cd.MainError.Nearest = argNode
63-
ctx.kind = fromFunctionArgument
64-
break
65-
}
66-
}
70+
ctx.origin = node.Text()
71+
parent := node.Parent()
72+
switch parent.Type() {
73+
case "field_access":
74+
ctx.kind = fromExpression
75+
case "argument_list":
76+
// if the offending line is an offending method call, get the argument that triggered the null error
77+
ctx.kind = fromFunctionArgument
78+
case "method_invocation":
79+
ctx.kind = fromMethodInvocation
80+
ctx.methodName = parent.ChildByFieldName("name").Text()
81+
ctx.origin = parent.ChildByFieldName("object").Text()
82+
case "assignment_expression":
83+
ctx.kind = fromAssignment
6784
}
68-
} else if exprNode.Type() == "assignment_expression" {
69-
// right := exprNode.ChildByFieldName("right")
70-
//
71-
}
7285

73-
// identify the *MAIN* culprit
74-
mainNode := cd.MainError.Nearest
75-
switch mainNode.Type() {
76-
case "method_invocation":
77-
nameNode := mainNode.ChildByFieldName("name")
78-
ctx.methodName = nameNode.Text()
79-
ctx.origin = mainNode.ChildByFieldName("object").Text()
80-
default:
81-
ctx.origin = mainNode.Text()
86+
ctx.parent = parent
87+
m.Nearest = node
88+
break
8289
}
83-
84-
err.Context = ctx
8590
}
91+
92+
m.Context = ctx
8693
},
8794
OnGenExplainFn: func(cd *lib.ContextData, gen *lib.ExplainGenerator) {
8895
// TODO: create a function that will find the node with a null return type
8996
ctx := cd.MainError.Context.(nullPointerExceptionCtx)
9097

9198
if ctx.kind == fromSystemOut {
92-
gen.Add("Your program tried to print the value of ")
99+
gen.Add("The error occurs due to your program tried to print the value of ")
93100
if len(ctx.methodName) != 0 {
94101
gen.Add("\"%s\" method from ", ctx.methodName)
95102
}
@@ -99,20 +106,58 @@ var NullPointerException = lib.ErrorTemplate{
99106
// if inArray {
100107
// gen.Add("Your program tried to execute the \"%s\" method from \"%s\" which is a null.", )
101108
// } else {
102-
gen.Add("Your program tried to execute the \"%s\" method from \"%s\" which is a null.", ctx.methodName, ctx.origin)
109+
gen.Add("The error occurs due to your program tried to execute the \"%s\" method from \"%s\" which is a null.", ctx.methodName, ctx.origin)
103110
// }
104111
return
105112
}
106113

107114
gen.Add("Your program try to access or manipulate an object reference that is currently pointing to `null`, meaning it doesn't refer to any actual object in memory. This typically happens when you forget to initialize an object before using it, or when you try to access an object that hasn't been properly assigned a value. ")
108115
},
109116
OnGenBugFixFn: func(cd *lib.ContextData, gen *lib.BugFixGenerator) {
110-
gen.Add("Wrap with an if statement", func(s *lib.BugFixSuggestion) {
111-
s.AddDescription("Check for the variable that is being used as `null`.")
112-
})
117+
ctx := cd.MainError.Context.(nullPointerExceptionCtx)
118+
parent := ctx.parent
119+
for parent.Type() != "expression_statement" {
120+
if parent.Parent().IsNull() {
121+
break
122+
}
123+
parent = parent.Parent()
124+
}
125+
126+
if parent.Type() == "expression_statement" {
127+
spaces := cd.MainError.Document.LineAt(parent.StartPosition().Line)[:parent.StartPosition().Column]
128+
129+
gen.Add("Wrap with an if statement", func(s *lib.BugFixSuggestion) {
130+
s.AddStep("Check for the variable that is being used as `null`.").
131+
AddFix(lib.FixSuggestion{
132+
NewText: fmt.Sprintf("if (%s != null) {\n", ctx.origin) + strings.Repeat(spaces, 2),
133+
StartPosition: parent.StartPosition(),
134+
EndPosition: parent.StartPosition(),
135+
}).
136+
AddFix(lib.FixSuggestion{
137+
NewText: "\n" + spaces + "}\n",
138+
StartPosition: parent.EndPosition(),
139+
EndPosition: parent.EndPosition(),
140+
})
141+
})
142+
}
113143

114144
gen.Add("Initialize the variable", func(s *lib.BugFixSuggestion) {
115-
s.AddDescription("An alternative fix is to initialize the `test` variable with a non-null value before calling the method.")
145+
// get the original location of variable
146+
symbolTree := cd.InitOrGetSymbolTree(cd.MainDocumentPath())
147+
varSym := symbolTree.GetSymbolByNode(cd.MainError.Nearest)
148+
149+
loc := varSym.Location()
150+
varDeclNode := cd.MainError.Document.RootNode().NamedDescendantForPointRange(loc)
151+
if varDeclNode.Type() == "variable_declarator" {
152+
loc = varDeclNode.ChildByFieldName("value").Location()
153+
}
154+
155+
s.AddStep("An alternative fix is to initialize the `%s` variable with a non-null value before calling the method.", ctx.origin).
156+
AddFix(lib.FixSuggestion{
157+
NewText: getDefaultValueForType(lib.UnwrapReturnType(varSym)),
158+
StartPosition: loc.StartPos,
159+
EndPosition: loc.EndPos,
160+
})
116161
})
117162
},
118163
}

Diff for: error_templates/java/test_files/null_pointer_exception/test.txt

+15-9
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ Exception in thread "main" java.lang.NullPointerException
66
template: "Java.NullPointerException"
77
---
88
# NullPointerException
9-
This error occurs because the program is trying to access a method or property of a null object, in this case, trying to invoke the `toUpperCase()` method on a `null` string.
9+
The error occurs due to your program tried to execute the "toUpperCase" method from "test" which is a null.
1010
```
1111
String test = null;
1212
System.out.println(test.toUpperCase());
@@ -18,17 +18,23 @@ This error occurs because the program is trying to access a method or property o
1818
### 1. Wrap with an if statement
1919
Check for the variable that is being used as `null`.
2020
```diff
21-
String test = null;
22-
- System.out.println(test.toUpperCase());
23-
+ if (test != null) {
24-
+ System.out.println(test.toUpperCase());
25-
+ }
21+
public static void main(String args[]) {
22+
String test = null;
23+
- System.out.println(test.toUpperCase());
24+
+ if (test != null) {
25+
+ System.out.println(test.toUpperCase());
26+
+ }
27+
}
28+
}
2629
```
2730

2831
### 2. Initialize the variable
2932
An alternative fix is to initialize the `test` variable with a non-null value before calling the method.
3033
```diff
31-
- String test = null;
32-
+ String test = "example"; // Assign a non-null value
33-
System.out.println(test.toUpperCase());
34+
public class ShouldBeNull {
35+
public static void main(String args[]) {
36+
- String test = null;
37+
+ String test = "example";
38+
System.out.println(test.toUpperCase());
39+
}
3440
```

0 commit comments

Comments
 (0)