Skip to content

Commit

Permalink
feat: ✨ more accurate range finding for Snyk OSS [ROAD-699] (#20)
Browse files Browse the repository at this point in the history
  • Loading branch information
bastiandoetsch authored Mar 17, 2022
1 parent 9b1c400 commit 4988b58
Show file tree
Hide file tree
Showing 15 changed files with 680 additions and 72 deletions.
40 changes: 40 additions & 0 deletions ast/ast.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package ast

import (
"github.com/sourcegraph/go-lsp"
)

type Node struct {
Line int
StartChar int
EndChar int
DocOffset int64
Parent *Node
Children []*Node
Name string
Value string
Attributes map[string]string
}

type Tree struct {
Root *Node
Document lsp.DocumentURI
}

type Parser interface {
Parse(content []byte, uri lsp.DocumentURI) Tree
}

type Visitor interface {
visit(*Node)
}

func (n *Node) Accept(v Visitor) {
v.visit(n)
}

func (n *Node) Add(child *Node) *Node {
n.Children = append(n.Children, child)
child.Parent = n
return n
}
15 changes: 15 additions & 0 deletions ast/ast_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package ast

import (
"testing"

"github.com/stretchr/testify/assert"
)

func TestNodeAddChild(t *testing.T) {
var node = Node{}
var child = Node{}
node.Add(&child)
assert.NotNil(t, node.Children)
assert.Equal(t, &node, child.Parent)
}
101 changes: 101 additions & 0 deletions ast/maven/parser.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
package maven

import (
"encoding/xml"
"io"
"strings"

"github.com/rs/zerolog/log"
"github.com/sourcegraph/go-lsp"

"github.com/snyk/snyk-ls/ast"
)

type Parser struct {
tree ast.Tree
}

type dependency struct {
Group string `xml:"group"`
ArtifactId string `xml:"artifactId"`
Version string `xml:"version"`
Scope string `xml:"scope"`
}

func (p *Parser) Parse(content string, uri lsp.DocumentURI) ast.Tree {
tree := p.initTree(uri, content)
d := xml.NewDecoder(strings.NewReader(content))
var offset int64
for {
token, err := d.Token()
offset = d.InputOffset()
if token == nil || err == io.EOF {
// EOF means we're done.
break
} else if err != nil {
log.Err(err).Msg("Couldn't parse XML")
}

switch xmlType := token.(type) {

case xml.StartElement:
if xmlType.Name.Local == "dependency" {
var dep dependency
if err = d.DecodeElement(&dep, &xmlType); err != nil {
log.Err(err).Msg("Couldn't decode dependency")
}
offsetAfter := d.InputOffset()
node := p.addNewNodeTo(tree.Root, offset, offsetAfter, dep)
log.Debug().Interface("node", node).Str("uri", string(p.tree.Document)).Msg("Added dependency node")
}
default:
}
}
return tree
}

func (p *Parser) initTree(uri lsp.DocumentURI, content string) ast.Tree {
var currentLine = 0
root := ast.Node{
Line: currentLine,
StartChar: 0,
EndChar: -1,
DocOffset: 0,
Parent: nil,
Children: nil,
Name: string(uri),
Value: content,
}
p.tree = ast.Tree{
Root: &root,
Document: uri,
}
return p.tree
}

func (p *Parser) addNewNodeTo(parent *ast.Node, offsetBefore int64, offsetAfter int64, dep dependency) *ast.Node {
content := p.tree.Root.Value
contentInclusive := content[0:offsetAfter]
startTag := "<version>"
endTag := "</version"
versionStartOffset := strings.LastIndex(contentInclusive, startTag)
contentUntilVersion := content[0:versionStartOffset]
line := strings.Count(contentUntilVersion, "\n")
lineStartOffset := strings.LastIndex(contentUntilVersion, "\n")
versionValueStartOffset := versionStartOffset + len(startTag) - lineStartOffset - 1
versionValueEndOffset := strings.LastIndex(contentInclusive, endTag) - lineStartOffset - 1

node := ast.Node{
Line: line,
StartChar: versionValueStartOffset,
EndChar: versionValueEndOffset,
DocOffset: offsetBefore,
Parent: parent,
Children: nil,
Name: dep.ArtifactId,
Value: dep.Version,
Attributes: make(map[string]string),
}
parent.Add(&node)
return &node
}
28 changes: 28 additions & 0 deletions ast/maven/parser_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package maven

import (
"os"
"path/filepath"
"testing"

"github.com/sourcegraph/go-lsp"
"github.com/stretchr/testify/assert"
)

func TestCreateDependencyTree(t *testing.T) {
var testPath, _ = filepath.Abs("testdata/pom.xml")
var testContent, _ = os.ReadFile(testPath)
doc := lsp.DocumentURI(testPath)
parser := Parser{}
tree := parser.Parse(string(testContent), doc)
children := tree.Root.Children
assert.Len(t, children, 2, "Should have extracted 2 deps from pom.xml")

assert.Equal(t, 38, children[0].Line)
assert.Equal(t, 15, children[0].StartChar)
assert.Equal(t, 21, children[0].EndChar)

assert.Equal(t, 43, children[1].Line)
assert.Equal(t, 15, children[1].StartChar)
assert.Equal(t, 21, children[1].EndChar)
}
48 changes: 48 additions & 0 deletions ast/maven/testdata/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>io.snyk</groupId>
<artifactId>maven-goof</artifactId>
<version>1.0-SNAPSHOT</version>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>14</source>
<target>14</target>
</configuration>
</plugin>
</plugins>
</build>

<properties>
<maven.compiler.source>15</maven.compiler.source>
<maven.compiler.target>15</maven.compiler.target>
</properties>

<dependencies>
<!-- <dependency>-->
<!-- &lt;!&ndash; log4shell&ndash;&gt;-->
<!-- <groupId>org.apache.logging.log4j</groupId>-->
<!-- <artifactId>log4j-core</artifactId>-->
<!-- <version>2.14.1</version>-->
<!-- </dependency>-->
<dependency>
<!-- log4shell-->
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.14.1</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<version>2.14.1</version>
</dependency>
</dependencies>

</project>
29 changes: 29 additions & 0 deletions oss/maven_range_finder.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package oss

import (
"github.com/rs/zerolog/log"
"github.com/sourcegraph/go-lsp"

"github.com/snyk/snyk-ls/ast/maven"
)

type MavenRangeFinder struct {
doc lsp.TextDocumentItem
}

func (m *MavenRangeFinder) Find(issue ossIssue) lsp.Range {
searchPackage, version := introducingPackageAndVersion(issue)
log.Debug().Interface("issue", issue).Str("searchPackage", searchPackage).Str("searchVersion", version)
parser := maven.Parser{}
tree := parser.Parse(m.doc.Text, m.doc.URI)
for _, depNode := range tree.Root.Children {
if searchPackage == depNode.Name {
log.Debug().Interface("dependency", depNode).Str("issueId", issue.Id).Msg("Found dependency for issue")
return lsp.Range{
Start: lsp.Position{Line: depNode.Line, Character: depNode.StartChar},
End: lsp.Position{Line: depNode.Line, Character: depNode.EndChar},
}
}
}
return lsp.Range{}
}
52 changes: 52 additions & 0 deletions oss/maven_range_finder_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package oss

import (
"os"
"path/filepath"
"testing"

"github.com/sourcegraph/go-lsp"
"github.com/stretchr/testify/assert"

"github.com/snyk/snyk-ls/config/environment"
)

func TestMavenRangeFinder_Find(t *testing.T) {
environment.Load()
environment.Format = environment.FormatHtml

var issue = ossIssue{
Id: "testIssue",
Name: "SNYK-TEST-ISSUE-1",
Title: "THOU SHALL NOT PASS",
Severity: "1",
LineNumber: 0,
Description: "Getting into Moria is an issue!",
References: nil,
Version: "",
PackageManager: "maven",
From: []string{"[email protected]", "org.apache.logging.log4j:[email protected]"},
}
var testPath, _ = filepath.Abs("testdata/pom.xml")
var testContent, _ = os.ReadFile(testPath)
var doc = lsp.TextDocumentItem{
URI: lsp.DocumentURI(testPath),
LanguageID: "xml",
Version: 0,
Text: string(testContent),
}

expectedRange := lsp.Range{
Start: lsp.Position{
Line: 38,
Character: 15,
},
End: lsp.Position{
Line: 38,
Character: 21,
},
}

actualRange := findRange(issue, doc)
assert.Equal(t, expectedRange, actualRange)
}
41 changes: 41 additions & 0 deletions oss/npm_range_finder.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package oss

import (
"strings"

"github.com/rs/zerolog/log"
"github.com/sourcegraph/go-lsp"
)

type NpmRangeFinder struct {
doc lsp.TextDocumentItem
myRange lsp.Range
}

func (n *NpmRangeFinder) Find(issue ossIssue) lsp.Range {
searchPackage, _ := introducingPackageAndVersion(issue)
var lines = strings.Split(strings.ReplaceAll(n.doc.Text, "\r\n", "\n"), "\n")
var start lsp.Position
var end lsp.Position
for i := 0; i < len(lines); i++ {
line := lines[i]
log.Trace().Interface("issueId", issue.Id).Str("line", line).Msg("scanning line for " + searchPackage)
elems := strings.Split(line, ":")
if len(elems) > 1 {
jsonKey := strings.Trim(strings.Trim(elems[0], " "), "\"")
if jsonKey == searchPackage {
start.Line = i
start.Character = strings.Index(line, searchPackage) - 1
end.Line = i
end.Character = len(strings.ReplaceAll(line, ",", ""))
log.Debug().Str("issueId", issue.Id).Interface("start", start).Interface("end", end).Msg("found range for " + searchPackage)
break
}
}
}
n.myRange = lsp.Range{
Start: start,
End: end,
}
return n.myRange
}
Loading

0 comments on commit 4988b58

Please sign in to comment.