Skip to content

Commit 4e09ae5

Browse files
Lenny HalsethLenny Halseth
authored andcommitted
Use Java Parser to get start lines for Java methods and enable scrolling to method in source viewer
1 parent ea88efd commit 4e09ae5

File tree

7 files changed

+131
-30
lines changed

7 files changed

+131
-30
lines changed

build.sbt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,7 @@ lazy val CodePulse = Project("CodePulse", file("codepulse"))
190190
Dependencies.commons.io, Dependencies.commons.lang,
191191
Dependencies.concLinkedHashMap, Dependencies.juniversalchardet, Dependencies.dependencyCheckCore,
192192
Dependencies.slick, Dependencies.h2,
193-
Dependencies.antlr
193+
Dependencies.antlr,
194+
Dependencies.javaparser
194195
) ++ Dependencies.asm ++ Dependencies.jackson ++ Dependencies.jna ++ Dependencies.logging
195196
)

codepulse/src/main/resources/toserve/pages/projects/projects.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -248,7 +248,7 @@ $(document).ready(function(){
248248
selection.refresh = true
249249

250250
sourceView.setSourceView(mode, source)
251-
sourceView.scrollToLine(selection.metadata.methodStartLine - 1)
251+
sourceView.scrollToLine(selection.metadata.methodStartLine)
252252

253253
coverageUpdateSubscription = Bacon.onValues(Trace.coverageRecords, Trace.activeRecordingsProp, function(coverage, activeRecordings) {
254254
if (selection.refresh) {

codepulse/src/main/scala/com/secdec/codepulse/data/bytecode/parse/MethodDeclarationListener.scala renamed to codepulse/src/main/scala/com/secdec/codepulse/data/bytecode/parse/ParseListener.scala

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,41 @@ package com.secdec.codepulse.data.bytecode.parse
2121

2222
import com.secdec.codepulse.parsers.java9
2323
import com.secdec.codepulse.parsers.java9.{ Java9BaseListener, Java9Parser }
24+
import scala.collection.JavaConverters._
25+
26+
import org.antlr.v4.runtime.CharStreams
27+
import org.antlr.v4.runtime.misc.Interval
28+
29+
case class MethodInfo(name: String, startLine: Int)
30+
case class ClassInfo(name: String, memberClasses: List[ClassInfo], memberMethods: List[MethodInfo])
31+
32+
class ParseListener(callback: (String, Int) => Unit) extends Java9BaseListener {
33+
var currentClass: Option[ClassInfo] = None
34+
var currentMethods = List.empty[MethodInfo]
35+
36+
override def enterClassDeclaration(ctx: Java9Parser.ClassDeclarationContext) = {
37+
38+
}
39+
40+
override def exitClassDeclaration(ctx: Java9Parser.ClassDeclarationContext) = {
41+
42+
}
2443

25-
class MethodDeclarationListener(callback: (String, Int) => Unit) extends Java9BaseListener {
2644
override def enterMethodDeclaration(ctx: Java9Parser.MethodDeclarationContext) = {
27-
val name = ctx.getText()
2845
val start = ctx.start.getLine()
2946

47+
val modifiers = ctx.methodModifier().asScala.map(_.getText())
48+
val header = ctx.methodHeader().getText()
49+
50+
val name = modifiers.mkString("") + header
51+
3052
callback(name, start)
53+
54+
// val input = ctx.methodHeader().getInputStream()
55+
// val interval = new Interval(ctx.methodHeader().start.getStartIndex(), ctx.methodHeader().stop.getStopIndex())
56+
// val t = input.getText(interval)
57+
58+
val method = MethodInfo(name, start)
59+
currentMethods = method :: currentMethods
3160
}
3261
}

codepulse/src/main/scala/com/secdec/codepulse/data/storage/ZippedStorage.scala

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -132,8 +132,8 @@ class ZippedStorage(zipFile: ZipFile) extends Storage {
132132
protected def readEntries[T](filter: ZipEntry => Boolean, entryPath: Option[NestedPath], filename: String, stream: InputStream, recursive: Boolean = true)(read: (String, Option[NestedPath], ZipEntry, InputStream) => Unit): Unit = {
133133
val zipStream = new ZipInputStream(stream)
134134
try {
135-
val entryStream = Stream.continually(Try { zipStream.getNextEntry })
136-
.map(_.toOption.flatMap { Option(_) })
135+
val entryStream = Stream.continually(Try {zipStream.getNextEntry})
136+
.map(_.toOption.flatMap {Option(_)})
137137
.takeWhile(_.isDefined)
138138
.flatten
139139

@@ -155,14 +155,16 @@ class ZippedStorage(zipFile: ZipFile) extends Storage {
155155
}
156156

157157
if (entry.isDirectory) {
158-
read(filename, nestedPath, entry, zipStream) // caller may need to process directories, too
159-
readEntries(filter, nestedPath, s"$filename/${entry.getName}", new CloseShieldInputStream(zipStream), true)(read)
158+
if (filter(entry)) read(filename, nestedPath, entry, zipStream) // caller may need to process directories, too
159+
if (recursive) readEntries(filter, nestedPath, s"$filename/${entry.getName}", new CloseShieldInputStream(zipStream), recursive)(read)
160160
}
161161
else if (Storage.isZip(entry.getName) && recursive)
162-
readEntries(filter, nestedPath, s"$filename/${entry.getName}", new CloseShieldInputStream(zipStream), true)(read)
163-
else if(filter(entry))
162+
readEntries(filter, nestedPath, s"$filename/${entry.getName}", new CloseShieldInputStream(zipStream), recursive)(read)
163+
else if (filter(entry))
164164
read(filename, entryPath, entry, zipStream)
165165
}
166+
} catch {
167+
case ex: Exception => System.out.println(ex)
166168
} finally {
167169
zipStream.close
168170
}

codepulse/src/main/scala/com/secdec/codepulse/input/LanguageProcessor.scala

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,11 @@ trait LanguageProcessor {
3636
sourceExtensions.contains(extension)
3737
}
3838

39+
final def sourceType(typeExtension: String)(entry: ZipEntry): Boolean = {
40+
val extension = FilenameUtils.getExtension(entry.getName)
41+
typeExtension == extension
42+
}
43+
3944
def canProcess(storage: Storage): Boolean
4045
def process(storage: Storage, treeNodeData: TreeNodeDataAccess, sourceData: SourceDataAccess): Unit
4146
}

codepulse/src/main/scala/com/secdec/codepulse/input/bytecode/ByteCodeProcessor.scala

Lines changed: 83 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -23,24 +23,35 @@ import java.io.InputStream
2323
import scala.collection.mutable.{ HashMap, MultiMap, Set }
2424

2525
import akka.actor.{ Actor, Stash }
26-
import com.secdec.codepulse.data.bytecode.parse.MethodDeclarationListener
26+
import com.google.common.io.CharStreams
27+
import com.secdec.codepulse.data.bytecode.parse.ParseListener
2728
import com.secdec.codepulse.data.bytecode.{ AsmVisitors, CodeForestBuilder, CodeTreeNodeKind }
2829
import com.secdec.codepulse.data.jsp.{ JasperJspAdapter, JspAnalyzer }
2930
import com.secdec.codepulse.data.model.{ MethodSignatureNode, SourceDataAccess, TreeNodeDataAccess, TreeNodeImporter }
3031
import com.secdec.codepulse.data.storage.Storage
3132
import com.secdec.codepulse.events.GeneralEventBus
3233
import com.secdec.codepulse.input.pathnormalization.{ FilePath, NestedPath, PathNormalization }
3334
import com.secdec.codepulse.input.{ CanProcessFile, LanguageProcessor }
35+
import com.secdec.codepulse.parsers.java9.Java9Parser.CompilationUnitContext
3436
import com.secdec.codepulse.parsers.java9.{ Java9Lexer, Java9Parser }
3537
import com.secdec.codepulse.processing.{ ProcessEnvelope, ProcessStatus }
3638
import com.secdec.codepulse.processing.ProcessStatus.{ DataInputAvailable, ProcessDataAvailable }
3739
import com.secdec.codepulse.util.SmartLoader.Success
3840
import com.secdec.codepulse.util.SmartLoader
3941
import org.apache.commons.io.FilenameUtils
4042
import net.liftweb.common.Loggable
41-
import org.antlr.v4.runtime.CharStreams
42-
import org.antlr.v4.runtime.CommonTokenStream
43-
import org.antlr.v4.runtime.tree.ParseTreeWalker
43+
//import org.antlr.v4.runtime.CharStreams
44+
//import org.antlr.v4.runtime.CommonTokenStream
45+
//import org.antlr.v4.runtime.tree.ParseTreeWalker
46+
import org.apache.commons.io.input.CloseShieldInputStream
47+
import com.github.javaparser.JavaParser
48+
import com.github.javaparser.ParseException
49+
import com.github.javaparser.ast.body.MethodDeclaration
50+
import com.github.javaparser.ast.visitor.VoidVisitorAdapter
51+
import com.github.javaparser.ast.Node
52+
import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration
53+
import com.github.javaparser.ast.CompilationUnit
54+
import com.github.javaparser.ast.PackageDeclaration
4455

4556
class ByteCodeProcessor(eventBus: GeneralEventBus) extends Actor with Stash with LanguageProcessor with Loggable {
4657
val group = "Classes"
@@ -97,6 +108,15 @@ class ByteCodeProcessor(eventBus: GeneralEventBus) extends Actor with Stash with
97108
})
98109
}
99110

111+
val sourceMethods = new HashMap[String, List[(String, Int)]]
112+
storage.readEntries(sourceType("java") _) { (filename, entryPath, entry, contents) =>
113+
val methodsAndStarts = parseJava9(contents)
114+
sourceMethods.get(entry.getName()) match {
115+
case None => sourceMethods += (entry.getName() -> methodsAndStarts)
116+
case Some(entries) => sourceMethods += (entry.getName() -> (entries ::: methodsAndStarts))
117+
}
118+
}
119+
100120
def getPackageFromSig(sig: String): String = {
101121
val packageContainer = sig.split(";").take(1)
102122
val packagePart = packageContainer(0).split("/").dropRight(1)
@@ -118,8 +138,7 @@ class ByteCodeProcessor(eventBus: GeneralEventBus) extends Actor with Stash with
118138
if (!entry.isDirectory) {
119139
FilenameUtils.getExtension(entry.getName) match {
120140
case "class" =>
121-
val methods = AsmVisitors.parseMethodsFromClass(contents)
122-
val methodsAndStartLines = parseJava9(contents)
141+
val methods = AsmVisitors.parseMethodsFromClass(new CloseShieldInputStream(contents))
123142
for {
124143
(file, name, size, lineCount, methodStartLine) <- methods
125144
pkg = getPackageFromSig(name)
@@ -132,7 +151,15 @@ class ByteCodeProcessor(eventBus: GeneralEventBus) extends Actor with Stash with
132151
case Some(np) => authoritativePath(groupName, np).map (_.toString)
133152
case None => None
134153
}
135-
treeNode <- builder.getOrAddMethod(groupName, name, size, authority, Option(lineCount), Option(methodStartLine))
154+
methodsAndStartLines = authority.flatMap(sourceMethods.get)
155+
startLine = methodsAndStartLines.flatMap { l =>
156+
val aName = name.split(";").take(1)(0)
157+
val potentials = l.filter { case (qualifiedName, line) => qualifiedName == name.split(";").take(1)(0) }
158+
val selection = potentials.headOption.getOrElse((("", 0)))
159+
160+
Some(selection._2)
161+
}
162+
treeNode <- builder.getOrAddMethod(groupName, name, size, authority, Option(lineCount), startLine)
136163
} {
137164
methodCorrelationsBuilder += (name -> treeNode.id)
138165
}
@@ -193,24 +220,60 @@ class ByteCodeProcessor(eventBus: GeneralEventBus) extends Actor with Stash with
193220
}
194221

195222
private def parseJava9(contents: InputStream): List[(String, Int)] = {
196-
// contents to stream
197-
val stream = CharStreams.fromStream(contents)
223+
def mkString(s1: String, s2: String, sep: String): String = {
224+
if(s1.isEmpty) s2
225+
else if(s2.isEmpty) s1
226+
else s1 + sep + s2
227+
}
228+
229+
def getQualifiedClass(n: Node, s: String): String = {
230+
n match {
231+
case m: MethodDeclaration => if(m.getParentNode().isPresent()) {
232+
getQualifiedClass(m.getParentNode().get(), s)
233+
} else {
234+
s
235+
}
236+
case c: ClassOrInterfaceDeclaration => if(c.getParentNode().isPresent()) {
237+
getQualifiedClass(c.getParentNode().get(), mkString(c.getName().toString, s, "$"))//c.getName + "." + s)
238+
} else {
239+
s
240+
}
241+
case cu: CompilationUnit => if(cu.getPackageDeclaration().isPresent()) {
242+
getQualifiedClass(cu.getPackageDeclaration().get(), s)
243+
} else {
244+
s
245+
}
246+
case p: PackageDeclaration => mkString(p.getName().toString.replace(".", "/"), s, "/")//p.getName + "." + s
247+
}
248+
}
198249

199-
// create lexer, token, and parser
200-
val lexer = new Java9Lexer(stream)
201-
val tokens = new CommonTokenStream(lexer)
202-
val parser = new Java9Parser(tokens)
250+
var methodStarts = List[(String, Int)]()
203251

204-
// read input
205-
val ctx = parser.methodDeclaration()
252+
try {
253+
val sourceContent = new CloseShieldInputStream(contents)
254+
val cu = JavaParser.parse(sourceContent)
206255

207-
// walk the tree, listening for what we need
208-
val walker = new ParseTreeWalker()
256+
val methodVisitor = new VoidVisitorAdapter[Void] {
257+
override def visit(n: MethodDeclaration, arg: Void): Unit = {
258+
super.visit(n, arg)
259+
System.out.println(getQualifiedClass(n, "") + "\t" + n.getName() + "\t" + n.getBegin())
209260

210-
var methodStarts = List[(String, Int)]()
211-
val listener = new MethodDeclarationListener((method, start) => methodStarts = (method, start) :: methodStarts)
261+
val qualifiedName = mkString(getQualifiedClass(n, ""), n.getName().toString, ".")
262+
val startLine = if(n.getBegin().isPresent()) {
263+
val location = n.getBegin().get()
264+
location.line
265+
} else {
266+
0
267+
}
212268

213-
walker.walk(listener, ctx)
269+
methodStarts = (qualifiedName, startLine) :: methodStarts
270+
}
271+
}
272+
273+
cu.accept(methodVisitor, null)
274+
} catch {
275+
case ex: Exception => System.out.println(ex)
276+
}
214277

215278
methodStarts
216279
}

project/Dependencies.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,4 +80,5 @@ object Dependencies {
8080
lazy val dispatch = "net.databinder.dispatch" %% "dispatch-core" % "0.11.4"
8181

8282
lazy val antlr = "org.antlr" % "antlr4-maven-plugin" % "4.7"
83+
lazy val javaparser = "com.github.javaparser" % "javaparser-core" % "3.6.13"
8384
}

0 commit comments

Comments
 (0)