Skip to content

Commit a61e808

Browse files
committed
Analyze JSP files to estimate their instruction count.
1 parent b991d76 commit a61e808

File tree

7 files changed

+168
-13
lines changed

7 files changed

+168
-13
lines changed

codepulse/src/main/scala/com/secdec/codepulse/data/jsp/JasperJspAdapter.scala

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -29,32 +29,33 @@ import com.secdec.codepulse.data.bytecode.CodeTreeNode
2929
* @author robertf
3030
*/
3131
class JasperJspAdapter extends JspAdapter {
32-
private val jsps = List.newBuilder[String]
32+
private val jsps = List.newBuilder[(String, Int)]
3333
private val webinfs = List.newBuilder[String]
3434

35-
def addJsp(path: String) = jsps += path
35+
def addJsp(path: String, size: Int) = jsps += path -> size
3636
def addWebinf(path: String) = webinfs += path
3737

3838
def build(codeForestBuilder: CodeForestBuilder): List[(String, Int)] = {
3939
val jsps = this.jsps.result
4040
val webinfs = this.webinfs.result
4141

42-
val jspClasses = Set.newBuilder[String]
42+
val jspClasses = Set.newBuilder[(String, Int)]
4343

4444
for (webinf <- webinfs) {
4545
val parent = Option(new File(webinf).getParent) getOrElse ""
4646
val parentLen = parent.length
4747
jspClasses ++= jsps
48-
.filter(_.startsWith(parent))
49-
.map(_.substring(parentLen))
48+
.filter(_._1.startsWith(parent))
49+
.map { case (clazz, size) => clazz.substring(parentLen) -> size }
5050
}
5151

5252
// build up
53-
jspClasses.result.toList map { clazz =>
54-
val jspClassName = JasperUtils.makeJavaClass(clazz)
55-
val displayName = clazz.split('/').filter(!_.isEmpty).toList
56-
val node = codeForestBuilder.getOrAddJsp(displayName, 100)
57-
jspClassName -> node.id
53+
jspClasses.result.toList map {
54+
case (clazz, size) =>
55+
val jspClassName = JasperUtils.makeJavaClass(clazz)
56+
val displayName = clazz.split('/').filter(!_.isEmpty).toList
57+
val node = codeForestBuilder.getOrAddJsp(displayName, size)
58+
jspClassName -> node.id
5859
}
5960
}
6061
}

codepulse/src/main/scala/com/secdec/codepulse/data/jsp/JspAdapter.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ import com.secdec.codepulse.data.bytecode.CodeForestBuilder
2626
* @author robertf
2727
*/
2828
trait JspAdapter {
29-
def addJsp(path: String): Unit
29+
def addJsp(path: String, size: Int): Unit
3030
def addWebinf(path: String): Unit
3131
def build(cfb: CodeForestBuilder): List[(String, Int)]
3232
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
/*
2+
* Code Pulse: A real-time code coverage testing tool. For more information
3+
* see http://code-pulse.com
4+
*
5+
* Copyright (C) 2014 Applied Visions - http://securedecisions.avi.com
6+
*
7+
* Licensed under the Apache License, Version 2.0 (the "License");
8+
* you may not use this file except in compliance with the License.
9+
* You may obtain a copy of the License at
10+
*
11+
* http://www.apache.org/licenses/LICENSE-2.0
12+
*
13+
* Unless required by applicable law or agreed to in writing, software
14+
* distributed under the License is distributed on an "AS IS" BASIS,
15+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16+
* See the License for the specific language governing permissions and
17+
* limitations under the License.
18+
*/
19+
20+
package com.secdec.codepulse.data.jsp
21+
22+
/** Analyzer for JSP files. The purpose is to make a rough approximation of
23+
* how many instructions are contained in a JSP file.
24+
*
25+
* We do this by scanning through the file and first counting how many
26+
* statements are within. Then we apply a multiplier to the statement count
27+
* and add a constant overhead value to approximate the number of instructions
28+
* for the file.
29+
*
30+
* @author robertf
31+
*/
32+
object JspAnalyzer {
33+
val ConstantOverhead = 96
34+
val LineMultiplier = 3
35+
val StatementMultiplier = 5
36+
37+
private val Block = (raw"(?s)<(%(?:=|!)?)(.*?)%>").r
38+
39+
private val ExpressionBlock = "%="
40+
private val ScriptBlock = "%"
41+
private val DeclarationBlock = "%!"
42+
43+
def analyze(contents: String) = {
44+
val blocks = Block.findAllMatchIn(contents).toList
45+
val statementCount = blocks.map { m =>
46+
val statementCount = (m group 2).count(_ == ';')
47+
48+
m group 1 match {
49+
case ExpressionBlock => 1 // count as 1 statement
50+
case ScriptBlock => 1 + statementCount // (1 + number of ';') statements
51+
case DeclarationBlock => statementCount // (number of ';') statements
52+
case _ => 0
53+
}
54+
}.sum
55+
56+
val blockLineCount = blocks.map(_ group 2).map(_ count (_ == '\n')).sum
57+
val lineCount = contents.count(_ == '\n') - blockLineCount
58+
59+
ConstantOverhead + StatementMultiplier * statementCount + LineMultiplier * lineCount
60+
}
61+
}

codepulse/src/main/scala/com/secdec/codepulse/tracer/TraceUploadData.scala

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,13 @@ import com.secdec.codepulse.data.bytecode.CodeForestBuilder
2525
import com.secdec.codepulse.data.trace.TraceId
2626
import com.secdec.codepulse.tracer.export.TraceImporter
2727
import com.secdec.codepulse.util.RichFile.RichFile
28+
import com.secdec.codepulse.util.SmartLoader
2829
import com.secdec.codepulse.util.ZipEntryChecker
2930
import net.liftweb.common.Box
3031
import net.liftweb.common.Failure
3132
import org.apache.commons.io.FilenameUtils
3233
import com.secdec.codepulse.data.jsp.JasperJspAdapter
34+
import com.secdec.codepulse.data.jsp.JspAnalyzer
3335
import com.secdec.codepulse.data.trace.TraceData
3436
import scala.concurrent.Future
3537
import scala.concurrent.ExecutionContext.Implicits.global
@@ -111,6 +113,8 @@ object TraceUploadData {
111113
//TODO: make this configurable somehow
112114
val jspAdapter = new JasperJspAdapter
113115

116+
val loader = new SmartLoader
117+
114118
ZipEntryChecker.forEachEntry(file) { (filename, entry, contents) =>
115119
val groupName = if (filename == file.getName) RootGroupName else s"JARs/$filename"
116120
if (!entry.isDirectory) {
@@ -123,7 +127,9 @@ object TraceUploadData {
123127
} methodCorrelationsBuilder += (name -> treeNode.id)
124128

125129
case "jsp" =>
126-
jspAdapter addJsp entry.getName
130+
val jspContents = loader loadStream contents
131+
val jspSize = JspAnalyzer analyze jspContents
132+
jspAdapter.addJsp(entry.getName, jspSize)
127133

128134
case _ => // nothing
129135
}
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
/*
2+
* Code Pulse: A real-time code coverage testing tool. For more information
3+
* see http://code-pulse.com
4+
*
5+
* Copyright (C) 2014 Applied Visions - http://securedecisions.avi.com
6+
*
7+
* Licensed under the Apache License, Version 2.0 (the "License");
8+
* you may not use this file except in compliance with the License.
9+
* You may obtain a copy of the License at
10+
*
11+
* http://www.apache.org/licenses/LICENSE-2.0
12+
*
13+
* Unless required by applicable law or agreed to in writing, software
14+
* distributed under the License is distributed on an "AS IS" BASIS,
15+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16+
* See the License for the specific language governing permissions and
17+
* limitations under the License.
18+
*/
19+
20+
package com.secdec.codepulse.util
21+
22+
import java.io.ByteArrayInputStream
23+
import java.io.ByteArrayOutputStream
24+
import java.io.InputStream
25+
26+
import scala.io.Codec
27+
import scala.io.Codec.string2codec
28+
import scala.io.Source
29+
30+
import org.mozilla.universalchardet.UniversalDetector
31+
32+
/** Loader that detects the proper character set when loading the contents of a
33+
* stream. Uses juniversalchardet.
34+
*
35+
* @author robertf
36+
*/
37+
class SmartLoader {
38+
private val BufferSize = 4096
39+
40+
val detector = new UniversalDetector(null)
41+
42+
/** Detect the character set used for `stream`.
43+
* *NOTE*: this consumes the stream.
44+
*/
45+
def detectCharset(stream: InputStream): String = {
46+
detector.reset
47+
48+
val buffer = new Array[Byte](BufferSize)
49+
50+
val chunks = Iterator.continually {
51+
stream.read(buffer, 0, buffer.length) -> buffer
52+
} takeWhile { _._1 > 0 }
53+
54+
for ((len, buffer) <- chunks.takeWhile(_ => !detector.isDone)) {
55+
detector.handleData(buffer, 0, len)
56+
}
57+
58+
detector.dataEnd
59+
detector.getDetectedCharset
60+
}
61+
62+
/** Detect charset for `stream` and decode all contents of the stream,
63+
* returning a string. This buffers the entire contents of the stream in
64+
* memory.
65+
*/
66+
def loadStream(stream: InputStream): String = {
67+
val bytes = {
68+
val bos = new ByteArrayOutputStream
69+
val buffer = new Array[Byte](BufferSize)
70+
71+
val chunks = Iterator.continually {
72+
stream.read(buffer, 0, buffer.length) -> buffer
73+
} takeWhile { _._1 > 0 }
74+
75+
for ((len, buffer) <- chunks) {
76+
bos.write(buffer, 0, len)
77+
}
78+
79+
bos.flush
80+
bos.toByteArray
81+
}
82+
83+
val charset = Option(detectCharset(new ByteArrayInputStream(bytes))).map[Codec](identity)
84+
Source.fromInputStream(new ByteArrayInputStream(bytes))(charset getOrElse Codec.UTF8).mkString
85+
}
86+
}

project/Build.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ object BuildDef extends Build with VersionSystem {
3535

3636
lazy val liftDependencies = Seq(lift_webkit, servletApi, logback, slf4j)
3737
lazy val testDependencies = Seq(junit, specs, scalatest)
38-
lazy val libDependencies = Seq(akka, reactive, jna, commons.io, concLinkedHashMap) ++ asm ++ jackson
38+
lazy val libDependencies = Seq(akka, reactive, jna, commons.io, concLinkedHashMap, juniversalchardet) ++ asm ++ jackson
3939
lazy val dbDependencies = Seq(slick, h2)
4040

4141
val baseCompilerSettings = Seq(

project/Dependencies.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ object Dependencies {
4646
"com.fasterxml.jackson.core" % "jackson-core" % "2.3.2",
4747
"com.fasterxml.jackson.module" %% "jackson-module-scala" % "2.3.2"
4848
)
49+
lazy val juniversalchardet = "com.googlecode.juniversalchardet" % "juniversalchardet" % "1.0.3"
4950

5051
// database related
5152
lazy val slick = "com.typesafe.slick" %% "slick" % "2.0.1"

0 commit comments

Comments
 (0)