Skip to content

Commit

Permalink
feat(java): add decorator support
Browse files Browse the repository at this point in the history
  • Loading branch information
cfabianski committed Jan 21, 2025
1 parent afb1456 commit 06556f8
Show file tree
Hide file tree
Showing 9 changed files with 121 additions and 2 deletions.
Binary file added java.test
Binary file not shown.
2 changes: 2 additions & 0 deletions pkg/languages/java/.snapshots/TestDecorator-App.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
{}

15 changes: 15 additions & 0 deletions pkg/languages/java/.snapshots/TestPattern-catch_class_decorator
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
(*builder.Result)({
Query: (string) (len=147) "([(class_declaration [(modifiers . [(annotation [ (identifier )] @param1 [ (annotation_argument_list )])] .)] name: (_) [ (class_body )])] @root)",
VariableNames: ([]string) (len=1) {
(string) (len=1) "_"
},
ParamToVariable: (map[string]string) {
},
EqualParams: ([][]string) <nil>,
ParamToContent: (map[string]map[string]string) (len=1) {
(string) (len=6) "param1": (map[string]string) (len=1) {
(string) (len=10) "identifier": (string) (len=14) "RequestMapping"
}
},
RootVariable: (*language.PatternVariable)(<nil>)
})
15 changes: 15 additions & 0 deletions pkg/languages/java/.snapshots/TestPattern-catch_function_decorator
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
(*builder.Result)({
Query: (string) (len=241) "([(class_declaration name: (_) [(class_body [(method_declaration . [(modifiers . [(annotation [ (identifier )] @param1 [ (annotation_argument_list )])] .)] type: (_) . name: (_) . [ (formal_parameters )] [ (block )] .)] @match )])] @root)",
VariableNames: ([]string) (len=1) {
(string) (len=1) "_"
},
ParamToVariable: (map[string]string) {
},
EqualParams: ([][]string) <nil>,
ParamToContent: (map[string]map[string]string) (len=1) {
(string) (len=6) "param1": (map[string]string) (len=1) {
(string) (len=10) "identifier": (string) (len=14) "RequestMapping"
}
},
RootVariable: (*language.PatternVariable)(<nil>)
})
16 changes: 16 additions & 0 deletions pkg/languages/java/analyzer/analyzer.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"slices"
"strings"

"github.com/rs/zerolog/log"
sitter "github.com/smacker/go-tree-sitter"

"github.com/bearer/bearer/pkg/scanner/ast/tree"
Expand Down Expand Up @@ -81,6 +82,8 @@ func (analyzer *analyzer) Analyze(node *sitter.Node, visitChildren func() error)
return analyzer.analyzeGenericOperation(node, visitChildren)
case "while_statement", "do_statement", "if_statement": // statements don't have results
return visitChildren()
case "marker_annotation", "annotation":
return analyzer.analyzeAnnotation(node, visitChildren)
default:
analyzer.builder.Dataflow(node, analyzer.builder.ChildrenFor(node)...)
return visitChildren()
Expand Down Expand Up @@ -145,6 +148,19 @@ func (analyzer *analyzer) analyzeCastExpression(node *sitter.Node, visitChildren
return visitChildren()
}

func (analyzer *analyzer) analyzeAnnotation(node *sitter.Node, visitChildren func() error) error {
name := node.ChildByFieldName("name")

log.Error().Msgf("analyzeAnnotation %s", analyzer.builder.ContentFor(name))
if arguments := node.ChildByFieldName("arguments"); arguments != nil {
analyzer.builder.Dataflow(node, arguments)
}

analyzer.lookupVariable(name)

return visitChildren()
}

// the "foo = 1" part in:
//
// class X {
Expand Down
17 changes: 17 additions & 0 deletions pkg/languages/java/java_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ var loggerRule []byte
//go:embed testdata/scope_rule.yml
var scopeRule []byte

//go:embed testdata/decorator.yml
var decoratorRule []byte

func TestImport(t *testing.T) {
testhelper.GetRunner(t, importRule, java.Get()).RunTest(t, "./testdata/import", ".snapshots/")
}
Expand All @@ -32,6 +35,10 @@ func TestScope(t *testing.T) {
testhelper.GetRunner(t, scopeRule, java.Get()).RunTest(t, "./testdata/scope", ".snapshots/")
}

func TestDecorator(t *testing.T) {
testhelper.GetRunner(t, decoratorRule, java.Get()).RunTest(t, "./testdata/decorator", ".snapshots/")
}

func TestPattern(t *testing.T) {
for _, test := range []struct{ name, pattern string }{
{"method params is a container type", `
Expand All @@ -46,6 +53,16 @@ func TestPattern(t *testing.T) {
}
}
`},
{"catch class decorator", `
$<!>@RequestMapping()
class $<_> {}
`},
{"catch function decorator", `
class $<...>$<_> $<...>{
$<!>@RequestMapping()
$<...>$<_> $<_>($<...>)$<...>{}
}
`},
} {
t.Run(test.name, func(tt *testing.T) {
result, err := patternquerybuilder.Build(java.Get(), test.pattern, "")
Expand Down
20 changes: 18 additions & 2 deletions pkg/languages/java/pattern/pattern.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,15 @@ var (
matchNodeRegex = regexp.MustCompile(`\$<!>`)
ellipsisRegex = regexp.MustCompile(`\$<\.\.\.>`)

matchNodeContainerTypes = []string{"catch_formal_parameter", "catch_type", "formal_parameters"}
matchNodeContainerTypes = []string{
"catch_formal_parameter",
"catch_type",
"formal_parameters",
"modifiers",
"method_declaration",
"class_declaration",
"program",
}

// todo: see if it is ok to replace typescripts `member_expression` with javas `field_access` and `method_invocation`
allowedQueryTypes = []string{"identifier", "type_identifier", "_", "field_access", "method_invocation", "string_literal"}
Expand Down Expand Up @@ -101,7 +109,15 @@ func (*Pattern) IsAnchored(node *tree.Node) (bool, bool) {
// function block
// lambda () -> {} block
// try {} catch () {}
unAnchored := []string{"class_declaration", "class_body", "block", "try_statement", "catch_type", "resource_specification"}
unAnchored := []string{
"class_declaration",
"class_body",
"block",
"try_statement",
"catch_type",
"resource_specification",
"modifiers",
}

isAnchored := !slices.Contains(unAnchored, parent.Type())
return isAnchored, isAnchored
Expand Down
17 changes: 17 additions & 0 deletions pkg/languages/java/testdata/decorator.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
languages:
- java
patterns:
- pattern: |
$<!>@RequestMapping($<...>) class $<...>$<_>$<...>{}
- pattern: |
class $<...>$<_> $<...>{
$<!>@GetMapping($<...>)
$<...>$<_> $<_>($<...>)$<...>{}
}
severity: high
metadata:
description: Test detection decorator
remediation_message: Test detection decorator
cwe_id:
- 42
id: decorator_test
21 changes: 21 additions & 0 deletions pkg/languages/java/testdata/decorator/App.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.example;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@SpringBootApplication
@RequestMapping("/api")
@RestController
public class App {
public static void main(String[] args) {
SpringApplication.run(SpringApiApp.class, args);
}

@GetMapping("/base-greet")
public String baseGreet() {
return "Hello from the base application class!";
}
}

0 comments on commit 06556f8

Please sign in to comment.