Skip to content

Commit ea51f61

Browse files
Lenny HalsethLenny Halseth
authored andcommitted
Enhance Java Parser capabilities
1 parent d405bc8 commit ea51f61

File tree

11 files changed

+1016
-7
lines changed

11 files changed

+1016
-7
lines changed
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
/*
2+
* Copyright 2018 Secure Decisions, a division of Applied Visions, Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*
16+
* This material is based on research sponsored by the Department of Homeland
17+
* Security (DHS) Science and Technology Directorate, Cyber Security Division
18+
* (DHS S&T/CSD) via contract number HHSP233201600058C.
19+
*/
20+
package com.secdec.codepulse.data.bytecode.parse
21+
22+
import org.objectweb.asm.{Opcodes => O}
23+
24+
/** Typesafe wrapper around an integer value that represents
25+
* the combination of any number of `Opcodes.ACC_*` values.
26+
*
27+
* Note that not all of the flags provided by this class are
28+
* necessarily used by Code Dx. They are simply mapped 1:1
29+
* from what is found in the javadocs at
30+
* http://asm.ow2.org/asm50/javadoc/user/org/objectweb/asm/Opcodes.html
31+
*
32+
* The `toStringFor...` methods try to choose a "sane" set of
33+
* flags to present for stringification.
34+
*/
35+
case class AccessFlags(bits: Int) extends AnyVal {
36+
37+
@inline def is(thoseBits: Int) = (bits & thoseBits) != 0
38+
39+
def isAbstract = is(O.ACC_ABSTRACT)
40+
def isAnnotation = is(O.ACC_ANNOTATION)
41+
def isBridge = is(O.ACC_BRIDGE)
42+
def isDeprecated = is(O.ACC_DEPRECATED)
43+
def isEnum = is(O.ACC_ENUM)
44+
def isFinal = is(O.ACC_FINAL)
45+
def isInterface = is(O.ACC_INTERFACE)
46+
def isMandated = is(O.ACC_MANDATED)
47+
def isNative = is(O.ACC_NATIVE)
48+
def isPrivate = is(O.ACC_PRIVATE)
49+
def isProtected = is(O.ACC_PROTECTED)
50+
def isPublic = is(O.ACC_PUBLIC)
51+
def isStatic = is(O.ACC_STATIC)
52+
def isStrict = is(O.ACC_STRICT)
53+
def isSuper = is(O.ACC_SUPER)
54+
def isSynchronized = is(O.ACC_SYNCHRONIZED)
55+
def isSynthetic = is(O.ACC_SYNTHETIC)
56+
def isTransient = is(O.ACC_TRANSIENT)
57+
def isVarargs = is(O.ACC_VARARGS)
58+
def isVolatile = is(O.ACC_VOLATILE)
59+
60+
private def stringifyWith(f: (String => Unit) => Unit) = {
61+
val sb = new StringBuilder
62+
var addedFirst = false
63+
def add(s: String): Unit = {
64+
if(addedFirst) sb += ' '
65+
else addedFirst = true
66+
sb append s
67+
}
68+
f(add _)
69+
sb.result()
70+
}
71+
72+
def toStringForClass = stringifyWith { add =>
73+
if(isPublic) add("public")
74+
if(isPrivate) add("private")
75+
if(isProtected) add("protected")
76+
77+
if(isFinal) add("final")
78+
if(isStatic) add("static")
79+
if(isAbstract) add("abstract")
80+
81+
if(isAnnotation) add("@interface")
82+
else if(isEnum) add("enum")
83+
else if(isInterface) add("interface")
84+
else add("class")
85+
}
86+
87+
def toStringForMethod = stringifyWith { add =>
88+
if(isPublic) add("public")
89+
if(isPrivate) add("private")
90+
if(isProtected) add("protected")
91+
92+
if(isFinal) add("final")
93+
if(isStatic) add("static")
94+
if(isAbstract) add("abstract")
95+
if(isNative) add("native")
96+
if(isSynchronized) add("synchronized")
97+
}
98+
99+
}
Lines changed: 200 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,200 @@
1+
/*
2+
* Copyright 2018 Secure Decisions, a division of Applied Visions, Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*
16+
* This material is based on research sponsored by the Department of Homeland
17+
* Security (DHS) Science and Technology Directorate, Cyber Security Division
18+
* (DHS S&T/CSD) via contract number HHSP233201600058C.
19+
*/
20+
package com.secdec.codepulse.data.bytecode.parse
21+
22+
import org.objectweb.asm.signature.SignatureVisitor
23+
24+
/** Represents a type in the JVM. Used for things like specifying the type of a method argument,
25+
* the super-class of a class, and more.
26+
*
27+
* There are four main "kinds" of type signature:
28+
* - BaseType (for primitives like 'int' and 'char'
29+
* - TypeVariable (for generics e.g. the 'A' in 'List<A>'
30+
* - ArrayType (for arrays of other types)
31+
* - ClassType (for arbitrary classes, possibly with generics)
32+
*/
33+
sealed trait TypeSignature
34+
object TypeSignature {
35+
case class BaseType(descriptor: Char) extends TypeSignature {
36+
override def toString = BaseType.namesMap.getOrElse(descriptor, descriptor.toString)
37+
}
38+
object BaseType {
39+
val ofByte = BaseType('B')
40+
val ofChar = BaseType('C')
41+
val ofDouble = BaseType('D')
42+
val ofFloat = BaseType('F')
43+
val ofInt = BaseType('I')
44+
val ofLong = BaseType('J')
45+
val ofShort = BaseType('S')
46+
val ofBoolean = BaseType('Z')
47+
val ofVoid = BaseType('V')
48+
49+
val byDescriptor = Map(
50+
'B' -> ofByte,
51+
'C' -> ofChar,
52+
'D' -> ofDouble,
53+
'F' -> ofFloat,
54+
'I' -> ofInt,
55+
'J' -> ofLong,
56+
'S' -> ofShort,
57+
'Z' -> ofBoolean,
58+
'V' -> ofVoid
59+
)
60+
61+
val namesMap = Map(
62+
'B' -> "byte",
63+
'C' -> "char",
64+
'D' -> "double",
65+
'F' -> "float",
66+
'I' -> "int",
67+
'J' -> "long",
68+
'S' -> "short",
69+
'Z' -> "boolean",
70+
'V' -> "void"
71+
)
72+
}
73+
74+
case class TypeVariable(name: String) extends TypeSignature {
75+
override def toString = name
76+
}
77+
case class ArrayType(elementType: TypeSignature) extends TypeSignature {
78+
override def toString = s"$elementType[]"
79+
}
80+
case class ClassType(name: ClassName, typeArguments: List[TypeArgument], innerClass: Option[ClassType]) extends TypeSignature {
81+
override def toString = {
82+
val sb = new StringBuilder
83+
sb append name
84+
if(typeArguments.nonEmpty) sb append typeArguments.mkString("<", ", ", ">")
85+
for(ic <- innerClass) sb append s".$ic"
86+
sb.result()
87+
}
88+
89+
def flatten: List[(ClassName, List[TypeArgument])] = {
90+
(name, typeArguments) :: innerClass.map(_.flatten).getOrElse(Nil)
91+
}
92+
}
93+
94+
def fuzzyEquals(left: TypeSignature, right: TypeSignature): Boolean = (left, right) match {
95+
case (BaseType(l), BaseType(r)) => l == r
96+
case (TypeVariable(l), TypeVariable(r)) => l == r
97+
case (ArrayType(l), ArrayType(r)) => fuzzyEquals(l, r)
98+
case (ClassType(lName, _, lInner), ClassType(rName, _, rInner)) =>
99+
// we'll ignore the type arguments, but make sure the name chains are fuzzily-equal
100+
val ls = lName.slashedName
101+
val rs = rName.slashedName
102+
val namesMatch = (ls endsWith rs) || (rs endsWith ls)
103+
if(namesMatch){
104+
(lInner, rInner) match {
105+
case (Some(li), Some(ri)) => fuzzyEquals(li, ri)
106+
case (None, None) => true
107+
case _ => false
108+
}
109+
} else {
110+
false
111+
}
112+
case _ => false
113+
}
114+
}
115+
116+
/** Represents a generic argument within a TypeSignature
117+
*/
118+
sealed trait TypeArgument
119+
object TypeArgument {
120+
// stands for a <?> type
121+
case object Unbounded extends TypeArgument
122+
123+
// stands for a <N> type where N is known, and may possibly be a <? extends N> or <? super N>
124+
case class Bounded(variance: TypeVariance, typeSig: TypeSignature) extends TypeArgument {
125+
import TypeVariance._
126+
override def toString = variance match {
127+
case InstanceOf => typeSig.toString
128+
case Super => s"super $typeSig"
129+
case Extends => s"extends $typeSig"
130+
}
131+
}
132+
133+
}
134+
135+
/** Represents the variance of a type argument, e.g. the difference between
136+
* `Foo<String>` and `Foo<? extends String>` and `Foo<? super String>`.
137+
* @param descriptor The character used by ASM to indicate a particular variance.
138+
* Possible values are '-', '+', and '='
139+
*/
140+
final case class TypeVariance private(descriptor: Char) extends AnyVal
141+
object TypeVariance {
142+
val InstanceOf = TypeVariance(SignatureVisitor.INSTANCEOF)
143+
val Super = TypeVariance(SignatureVisitor.SUPER)
144+
val Extends = TypeVariance(SignatureVisitor.EXTENDS)
145+
146+
def fromChar(char: Char) = char match {
147+
case '-' => Super
148+
case '+' => Extends
149+
case _ => InstanceOf
150+
}
151+
}
152+
153+
/** Represents a generic type parameter specified by a method or a class.
154+
* For example, in
155+
* {{{ class Foo<A extends Bar> }}}
156+
* There would be a `FormalTypeParameter("A", Some(ClassType("Bar"), Nil)`
157+
*
158+
* @param name
159+
* @param classBound
160+
* @param interfaceBounds
161+
*/
162+
case class FormalTypeParameter(name: String, classBound: Option[TypeSignature], interfaceBounds: List[TypeSignature]) {
163+
override def toString = {
164+
val allBounds = classBound match {
165+
case None => interfaceBounds
166+
case Some(cb) => cb :: interfaceBounds
167+
}
168+
val boundsStr = if(allBounds.nonEmpty) allBounds.mkString(" extends ", " & ", "") else ""
169+
name + boundsStr
170+
}
171+
}
172+
173+
/** Represents all of the types involved with a class's signature
174+
*
175+
* @param typeParams
176+
* @param superClass
177+
* @param interfaces
178+
*/
179+
case class JVMClassSignature(typeParams: List[FormalTypeParameter], superClass: TypeSignature, interfaces: List[TypeSignature])
180+
181+
/** Represents all of the types involved with a method's signature
182+
*
183+
* @param typeParams
184+
* @param paramTypes
185+
* @param returnType
186+
* @param exceptionTypes
187+
*/
188+
case class JVMMethodTypeSignature(typeParams: List[FormalTypeParameter], paramTypes: List[TypeSignature], returnType: TypeSignature, exceptionTypes: List[TypeSignature])
189+
190+
/** Size information about a piece of code (usually a method).
191+
*
192+
* @param instructionCount The number of bytecode instructions in the method's implementation.
193+
* Note that this may be 0 for abstract/interface methods.
194+
* @param lineCount The number of lines of code in the method's implementation, if any line-level
195+
* debug information was found in the bytecode.
196+
*/
197+
case class CodeSize(instructionCount: Int, lineCount: Option[Int])
198+
object CodeSize {
199+
implicit val codeSizeOrdering: Ordering[CodeSize] = Ordering.by { s => (s.instructionCount, s.lineCount) }
200+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
/*
2+
* Copyright 2018 Secure Decisions, a division of Applied Visions, Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*
16+
* This material is based on research sponsored by the Department of Homeland
17+
* Security (DHS) Science and Technology Directorate, Cyber Security Division
18+
* (DHS S&T/CSD) via contract number HHSP233201600058C.
19+
*/
20+
package com.secdec.codepulse.data.bytecode.parse
21+
22+
/** Represents a class's name.
23+
*
24+
* This class exists basically to give a bit of a type-level hint as to
25+
* whether the name is being expressed with "dot notation" (i.e. "java.lang.String")
26+
* or "slash notation" (i.e. "java/lang/String").
27+
*
28+
* Instances can only be created via the companion object's `fromDotted`
29+
* and `fromSlashed` methods.
30+
*/
31+
final case class ClassName private[ClassName](slashedName: String) extends AnyVal {
32+
def dottedName = slashedName.replace('/', '.')
33+
override def toString = dottedName
34+
}
35+
36+
object ClassName {
37+
def fromDotted(dottedName: String) = new ClassName(dottedName.replace('.', '/'))
38+
def fromSlashed(slashedName: String) = new ClassName(slashedName)
39+
}

0 commit comments

Comments
 (0)