|
| 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 | +} |
0 commit comments