PageRenderTime 49ms CodeModel.GetById 18ms RepoModel.GetById 0ms app.codeStats 1ms

/DEVELOPING_OPAL/tools/src/main/scala/org/opalj/hermes/queries/MicroPatterns.scala

https://bitbucket.org/delors/opal
Scala | 493 lines | 398 code | 48 blank | 47 comment | 141 complexity | ba88e1e796d75522efa5eaf19ce7d787 MD5 | raw file
  1. /* BSD 2-Clause License - see OPAL/LICENSE for details. */
  2. package org.opalj
  3. package hermes
  4. package queries
  5. import org.opalj.br.ClassFile
  6. import org.opalj.br.Method
  7. import org.opalj.br.Field
  8. import org.opalj.br.PC
  9. import org.opalj.br.ObjectType
  10. import org.opalj.br.MethodDescriptor
  11. import org.opalj.br.analyses.SomeProject
  12. import org.opalj.br.analyses.FieldAccessInformation
  13. import org.opalj.br.analyses.FieldAccessInformationKey
  14. import org.opalj.br.analyses.Project
  15. import org.opalj.br.instructions.Instruction
  16. import org.opalj.br.instructions.ArrayLoadInstruction
  17. import org.opalj.br.instructions.FieldReadAccess
  18. import org.opalj.br.instructions.FieldWriteAccess
  19. import org.opalj.br.instructions.LoadConstantInstruction
  20. import org.opalj.br.instructions.MethodInvocationInstruction
  21. import org.opalj.br.instructions.PUTFIELD
  22. import org.opalj.br.instructions.PUTSTATIC
  23. import org.opalj.br.instructions.ReturnValueInstruction
  24. import org.opalj.br.instructions.VirtualMethodInvocationInstruction
  25. import org.opalj.ai.BaseAI
  26. import org.opalj.ai.CorrelationalDomain
  27. import org.opalj.ai.domain
  28. import org.opalj.br.LongType
  29. /**
  30. * Counts which kinds of micro patterns are actually available.
  31. *
  32. * @author Leonid Glanz
  33. */
  34. class MicroPatterns(implicit hermes: HermesConfig) extends FeatureQuery {
  35. override val featureIDs: List[String] = {
  36. List(
  37. /*0*/ "Designator",
  38. /*1*/ "Taxonomy",
  39. /*2*/ "Joiner",
  40. /*3*/ "Pool",
  41. /*4*/ "Function Pointer",
  42. /*5*/ "Function Object",
  43. /*6*/ "Cobol Like",
  44. /*7*/ "Stateless",
  45. /*8*/ "Common State",
  46. /*9*/ "Immutable",
  47. /*10*/ "Restricted Creation",
  48. /*11*/ "Sampler",
  49. /*12*/ "Box",
  50. /*13*/ "Compound Box",
  51. /*14*/ "Canopy",
  52. /*15*/ "Record",
  53. /*16*/ "Data Manager",
  54. /*17*/ "Sink",
  55. /*18*/ "Outline",
  56. /*19*/ "Trait",
  57. /*20*/ "State Machine",
  58. /*21*/ "Pure Type",
  59. /*22*/ "Augmented Type",
  60. /*23*/ "Pseudo Class",
  61. /*24*/ "Implementor",
  62. /*25*/ "Overrider",
  63. /*26*/ "Extender"
  64. )
  65. }
  66. override def apply[S](
  67. projectConfiguration: ProjectConfiguration,
  68. project: Project[S],
  69. rawClassFiles: Traversable[(org.opalj.da.ClassFile, S)]
  70. ): TraversableOnce[Feature[S]] = {
  71. implicit val theProject = project
  72. val fa = project.get(FieldAccessInformationKey)
  73. val microPatternLocations = Array.fill(featureIDs.size)(new LocationsContainer[S])
  74. for {
  75. (classFile, source) project.projectClassFilesWithSources
  76. if !isInterrupted()
  77. } {
  78. val location = ClassFileLocation(source, classFile)
  79. if (isDesignator(classFile)) microPatternLocations(0) += location
  80. if (isTaxonomy(classFile)) microPatternLocations(1) += location
  81. if (isJoiner(classFile)) microPatternLocations(2) += location
  82. if (isPool(classFile)) microPatternLocations(3) += location
  83. if (isFunctionPointer(classFile)) microPatternLocations(4) += location
  84. if (isFunctionObject(classFile)) microPatternLocations(5) += location
  85. if (isCobolLike(classFile)) microPatternLocations(6) += location
  86. if (isStateless(classFile)) microPatternLocations(7) += location
  87. if (isCommonState(classFile)) microPatternLocations(8) += location
  88. if (isImmutable(classFile, fa)) microPatternLocations(9) += location
  89. if (isRestrictedCreation(classFile)) microPatternLocations(10) += location
  90. if (isSampler(classFile)) microPatternLocations(11) += location
  91. if (isBox(classFile, fa)) microPatternLocations(12) += location
  92. if (isCompoundBox(classFile, fa)) microPatternLocations(13) += location
  93. if (isCanopy(classFile, fa)) microPatternLocations(14) += location
  94. if (isRecord(classFile)) microPatternLocations(15) += location
  95. if (isDataManager(classFile)) microPatternLocations(16) += location
  96. if (isSink(classFile)) microPatternLocations(17) += location
  97. if (isOutline(classFile)) microPatternLocations(18) += location
  98. if (isTrait(classFile)) microPatternLocations(19) += location
  99. if (isStateMachine(classFile)) microPatternLocations(20) += location
  100. if (isPureType(classFile)) microPatternLocations(21) += location
  101. if (isAugmentedType(classFile)) microPatternLocations(22) += location
  102. if (isPseudoClass(classFile)) microPatternLocations(23) += location
  103. if (isImplementor(classFile, project)) microPatternLocations(24) += location
  104. if (isOverrider(classFile, project)) microPatternLocations(25) += location
  105. if (isExtender(classFile)) microPatternLocations(26) += location
  106. }
  107. for { (featureID, featureIDIndex) featureIDs.iterator.zipWithIndex } yield {
  108. Feature[S](featureID, microPatternLocations(featureIDIndex))
  109. }
  110. }
  111. def hasExplicitSuperType(cl: ClassFile): Boolean = cl.superclassType.exists(_ ne ObjectType.Object)
  112. /**
  113. * [From the paper] Thus, a Designator micro pattern is an interface which does not
  114. * declare any methods, does not define any static fields or methods, and does not
  115. * inherit such members from any of its superinterfaces.
  116. *
  117. * A class can also be Designator if its definition, as well as the definitions of
  118. * all of its ancestors (other than Object), are empty.
  119. *
  120. * A class can also be Designator if its definition, as well as the definitions of
  121. * all of its ancestors (other than Object), are empty.
  122. */
  123. def isDesignator(cl: ClassFile)(implicit project: SomeProject): Boolean = {
  124. if (cl.thisType == ObjectType.Object)
  125. return false;
  126. // IMPROVE Cache the results of super interfaces to avoid recomputations or compute it top-down starting with top-level interfaces.
  127. def isDesignatorType(ot: ObjectType): Boolean = {
  128. project.classFile(ot).exists(this.isDesignator)
  129. }
  130. cl.fields.isEmpty && {
  131. if (cl.isInterfaceDeclaration)
  132. cl.methods.isEmpty && cl.interfaceTypes.forall(isDesignatorType)
  133. else
  134. // we have to filter the always present default constructor (compiler generated,
  135. // if not user defined)
  136. cl.methods.size == 1 && cl.methods.head.descriptor == MethodDescriptor.NoArgsAndReturnVoid &&
  137. cl.interfaceTypes.forall(isDesignatorType) &&
  138. isDesignatorType(cl.superclassType.get)
  139. }
  140. }
  141. /**
  142. * [From the paper:] An empty interface which extends a single interface is
  143. * called a Taxonomy, since it is included, in the subtyping sense, in its parent,
  144. * but otherwise identical to it.
  145. *
  146. * There are also classes which are Taxonomy. Such a class must similarly be empty,
  147. * i.e., add no fields nor methods to its parent. Since constructors are not inherited,
  148. * an empty class may contain constructors. A Taxonomy class may not implement any interfaces.
  149. */
  150. def isTaxonomy(cl: ClassFile): Boolean = {
  151. cl.fields.isEmpty && {
  152. if (cl.isInterfaceDeclaration) {
  153. cl.interfaceTypes.size == 1 && cl.methods.isEmpty
  154. } else {
  155. cl.thisType != ObjectType.Object /*this test is not necessary, but is fast */ &&
  156. cl.interfaceTypes.isEmpty &&
  157. cl.methods.forall(_.isInitializer)
  158. }
  159. }
  160. }
  161. /**
  162. * [From the paper:] An empty interface which extends more than one interface is called a
  163. * Joiner, since in effect, it joins together the sets of members of its parents.
  164. *
  165. * An empty class which implements one or more interfaces is also a Joiner.
  166. * ''Here, empty means that the we can have constructors and (optionally) a serialVersionUID
  167. * field.''
  168. */
  169. def isJoiner(cl: ClassFile): Boolean = {
  170. if (cl.isInterfaceDeclaration) {
  171. cl.interfaceTypes.size > 1 && cl.fields.isEmpty && cl.methods.isEmpty
  172. } else {
  173. cl.interfaceTypes.nonEmpty &&
  174. cl.methods.forall(m m.isInitializer) && (
  175. cl.fields match {
  176. case Seq() | Seq(Field(_, "serialVersionUID", LongType)) true
  177. case _ false
  178. }
  179. )
  180. }
  181. }
  182. /**
  183. * [From the paper:] The most degenerate classes are those which have neither state
  184. * nor behavior. Such a class is distinguished by the requirement that it declares
  185. * no instance fields. Moreover, all of its declared static fields must be final.
  186. * Another requirement is that the class has no methods (other than those inherited
  187. * from Object, or automatically generated constructors).
  188. */
  189. def isPool(cl: ClassFile): Boolean = {
  190. cl.fields.nonEmpty && cl.fields.forall(f f.isFinal && f.isStatic) &&
  191. // We also (have to) accept a static initializer, because that one will
  192. // initialize the final static fields!
  193. cl.methods.forall(m m.isInitializer && m.descriptor.parametersCount == 0)
  194. }
  195. private final val javaLangObjectMethods: Set[String] = Set(
  196. "hashCode", "equals",
  197. "notify", "notifyAll", "wait",
  198. "getClass", "clone", "toString", "finalize"
  199. )
  200. def isObjectMethod(method: Method): Boolean = {
  201. // TODO This just checks the name, why don't we check the full signature?
  202. javaLangObjectMethods.contains(method.name)
  203. }
  204. def isFunctionPointer(cl: ClassFile): Boolean = {
  205. !cl.isInterfaceDeclaration && !cl.isAbstract && cl.methods.count { m
  206. !isInitMethod(m)
  207. } == 1 && cl.methods.count { m
  208. m.isPublic
  209. } == 1 && !cl.methods.exists(m m.isStatic &&
  210. !m.isStaticInitializer) && cl.fields.isEmpty
  211. }
  212. def isFunctionObject(cl: ClassFile): Boolean = {
  213. cl.fields.nonEmpty &&
  214. cl.fields.forall { f !f.isStatic } &&
  215. cl.methods.count { m !isInitMethod(m) && m.isPublic } == 1 &&
  216. !cl.methods.filter(m !isInitMethod(m)).exists(m m.isStatic)
  217. }
  218. def isCobolLike(cl: ClassFile): Boolean = {
  219. !cl.methods.exists { m
  220. !isInitMethod(m)
  221. } && cl.methods.count { m
  222. !isInitMethod(m) &&
  223. m.isStatic
  224. } == 1 &&
  225. !cl.fields.exists { f !f.isStatic } && cl.fields.exists(f f.isStatic)
  226. }
  227. def isStateless(cl: ClassFile): Boolean = {
  228. !cl.isInterfaceDeclaration && !cl.isAbstract &&
  229. !cl.fields.exists { f !(f.isFinal && f.isStatic) } &&
  230. cl.methods.count(m !isInitMethod(m) && !isObjectMethod(m)) > 1
  231. }
  232. def isCommonState(cl: ClassFile): Boolean = {
  233. !cl.isInterfaceDeclaration &&
  234. cl.fields.nonEmpty && cl.fields.forall { f f.isStatic } &&
  235. cl.fields.exists { f !f.isFinal }
  236. }
  237. def isImmutable(cl: ClassFile, fa: FieldAccessInformation): Boolean = {
  238. !cl.isInterfaceDeclaration &&
  239. !cl.isAbstract && cl.fields.count { f !f.isStatic } > 1 &&
  240. cl.fields.forall(f f.isPrivate && !f.isStatic) &&
  241. cl.fields.forall { f
  242. !fa.allWriteAccesses.contains(f) ||
  243. fa.allWriteAccesses(f).forall(p isInitMethod(p._1))
  244. }
  245. }
  246. def isRestrictedCreation(cl: ClassFile): Boolean = {
  247. !cl.isInterfaceDeclaration &&
  248. cl.fields.exists { f f.isStatic && !f.isFinal && f.fieldType == cl.thisType } &&
  249. cl.methods.filter { m m.isConstructor }.forall { m m.isPrivate }
  250. }
  251. def isSampler(cl: ClassFile): Boolean = {
  252. !cl.isInterfaceDeclaration &&
  253. cl.methods.filter { m m.isConstructor }.exists { m m.isPublic } &&
  254. cl.fields.exists { f
  255. f.isStatic &&
  256. f.fieldType.toJava.equals(cl.thisType.toJava)
  257. }
  258. }
  259. def isBox(cl: ClassFile, fa: FieldAccessInformation): Boolean = {
  260. !cl.isInterfaceDeclaration &&
  261. cl.fields.count { f !f.isStatic } == 1 &&
  262. cl.fields.count { f !f.isFinal } == 1 &&
  263. cl.fields.exists(f fa.allWriteAccesses.contains(f) &&
  264. fa.allWriteAccesses(f).exists(t cl.methods.contains(t._1)))
  265. }
  266. def isCompoundBox(cl: ClassFile, fa: FieldAccessInformation): Boolean = {
  267. !cl.isInterfaceDeclaration &&
  268. cl.fields.count(f f.fieldType.isReferenceType && !f.isStatic &&
  269. !f.isFinal && fa.allWriteAccesses.contains(f) &&
  270. fa.allWriteAccesses(f).exists(t cl.methods.contains(t._1))) == 1 &&
  271. cl.fields.count(f !f.isStatic && !f.fieldType.isReferenceType) + 1 == cl.fields.size
  272. }
  273. def isCanopy(cl: ClassFile, fa: FieldAccessInformation): Boolean = {
  274. !cl.isInterfaceDeclaration &&
  275. cl.fields.count { f !f.isStatic } == 1 &&
  276. cl.fields.count { f !f.isStatic && !f.isPublic } == 1 &&
  277. cl.fields.exists { f
  278. !f.isStatic && fa.allWriteAccesses.contains(f) &&
  279. fa.allWriteAccesses(f).forall(p isInitMethod(p._1))
  280. }
  281. }
  282. def isRecord(cl: ClassFile): Boolean = {
  283. !cl.isInterfaceDeclaration && cl.fields.nonEmpty &&
  284. cl.fields.forall { f f.isPublic } && cl.fields.exists(f !f.isStatic) && cl.methods.forall(m isInitMethod(m) || isObjectMethod(m))
  285. }
  286. def isSink(cl: ClassFile): Boolean = {
  287. !cl.isInterfaceDeclaration &&
  288. cl.methods.exists { m !isInitMethod(m) } &&
  289. cl.methods.forall { m
  290. m.body.isEmpty ||
  291. m.body.get.instructions.filter(i i.isInstanceOf[MethodInvocationInstruction]).forall { i
  292. !i.asInstanceOf[MethodInvocationInstruction].declaringClass.isObjectType ||
  293. i.asInstanceOf[MethodInvocationInstruction].declaringClass.asObjectType.equals(cl.thisType)
  294. }
  295. }
  296. }
  297. def isOutline(cl: ClassFile): Boolean = {
  298. !cl.isInterfaceDeclaration && cl.isAbstract && cl.methods.count { m
  299. m.body.isDefined &&
  300. m.body.get.instructions.exists { i
  301. i.isInstanceOf[VirtualMethodInvocationInstruction] &&
  302. i.asInstanceOf[VirtualMethodInvocationInstruction].declaringClass.equals(cl.thisType) &&
  303. cl.methods.filter { x x.isAbstract }.exists { x
  304. x.name.equals(i.asInstanceOf[VirtualMethodInvocationInstruction].name) &&
  305. x.descriptor.equals(i.asInstanceOf[VirtualMethodInvocationInstruction].methodDescriptor)
  306. }
  307. }
  308. } > 1
  309. }
  310. def isTrait(cl: ClassFile): Boolean = {
  311. !cl.isInterfaceDeclaration && cl.isAbstract &&
  312. cl.fields.isEmpty &&
  313. cl.methods.exists(m m.isAbstract)
  314. }
  315. def isStateMachine(cl: ClassFile): Boolean = {
  316. cl.methods.count(m !isInitMethod(m)) > 1 &&
  317. cl.fields.nonEmpty &&
  318. cl.methods.forall { m m.descriptor.parametersCount == 0 }
  319. }
  320. def isPureType(cl: ClassFile): Boolean = {
  321. (
  322. (
  323. cl.isAbstract && cl.methods.nonEmpty &&
  324. cl.methods.forall { m m.isAbstract && !m.isStatic }
  325. ) ||
  326. (
  327. cl.isInterfaceDeclaration && cl.methods.nonEmpty && cl.methods.forall { m !m.isStatic }
  328. )
  329. ) &&
  330. cl.fields.isEmpty
  331. }
  332. def isAugmentedType(cl: ClassFile): Boolean = {
  333. !cl.isInterfaceDeclaration && cl.isAbstract &&
  334. cl.methods.forall { m m.isAbstract } && cl.fields.size >= 3 &&
  335. cl.fields.forall { f f.isFinal && f.isStatic } &&
  336. cl.fields.map { f f.fieldType }.toSet.size == 1
  337. }
  338. def isPseudoClass(cl: ClassFile): Boolean = {
  339. !cl.isInterfaceDeclaration &&
  340. cl.fields.forall { f f.isStatic } && cl.methods.nonEmpty &&
  341. cl.methods.forall { m m.isAbstract || m.isStatic }
  342. }
  343. def isImplementor[S](cl: ClassFile, theProject: Project[S]): Boolean = {
  344. !cl.isInterfaceDeclaration &&
  345. !cl.isAbstract && cl.methods.exists { m
  346. m.isPublic &&
  347. !isInitMethod(m)
  348. } && cl.methods.forall { m
  349. isInitMethod(m) || !m.isPublic ||
  350. (theProject.resolveMethodReference(cl.thisType, m.name, m.descriptor) match {
  351. case Some(a) (a.isAbstract || a.body.isEmpty) && (
  352. (hasExplicitSuperType(cl) && a.classFile != null &&
  353. a.classFile.thisType == cl.superclassType.get) ||
  354. cl.interfaceTypes.exists(it a.classFile != null && a.classFile.thisType == it)
  355. )
  356. case None false
  357. })
  358. }
  359. }
  360. def isInitMethod(method: Method): Boolean = method.isInitializer
  361. def isOverrider[S](cl: ClassFile, theProject: Project[S]): Boolean = {
  362. !cl.isInterfaceDeclaration && !cl.isAbstract &&
  363. cl.methods.exists { m
  364. !isInitMethod(m)
  365. } && cl.methods.forall { m
  366. m.isInitializer ||
  367. (theProject.resolveMethodReference(cl.thisType, m.name, m.descriptor) match {
  368. case Some(a) (!a.isAbstract && a.body.isDefined && m.body.nonEmpty) && (
  369. (hasExplicitSuperType(cl) && a.classFile != null &&
  370. a.classFile.thisType == cl.superclassType.get) ||
  371. cl.interfaceTypes.exists(it a.classFile != null && a.classFile.thisType == it)
  372. )
  373. case None false
  374. })
  375. }
  376. }
  377. def isExtender[S](cl: ClassFile)(implicit theProject: Project[S]): Boolean = {
  378. !cl.isInterfaceDeclaration &&
  379. cl.methods.exists(m !isInitMethod(m)) && hasExplicitSuperType(cl) && cl.methods.forall { m
  380. isInitMethod(m) ||
  381. theProject.resolveMethodReference(cl.thisType, m.name, m.descriptor).isEmpty
  382. }
  383. }
  384. def isDataManager[S](cl: ClassFile)(implicit theProject: Project[S]): Boolean = {
  385. !cl.isInterfaceDeclaration &&
  386. !cl.isAbstract &&
  387. cl.fields.nonEmpty &&
  388. cl.methods.count(m !isInitMethod(m) && !isObjectMethod(m)) > 1 &&
  389. cl.methods.filter(m !isInitMethod(m) && !isObjectMethod(m)).forall { m
  390. isSetter(m) || isGetter(m)
  391. }
  392. }
  393. class AnalysisDomain[S](val project: Project[S], val method: Method)
  394. extends CorrelationalDomain
  395. with domain.DefaultHandlingOfMethodResults
  396. with domain.IgnoreSynchronization
  397. with domain.ThrowAllPotentialExceptionsConfiguration
  398. with domain.l0.DefaultTypeLevelFloatValues
  399. with domain.l0.DefaultTypeLevelDoubleValues
  400. with domain.l0.TypeLevelFieldAccessInstructions
  401. with domain.l0.TypeLevelInvokeInstructions
  402. with domain.l1.DefaultReferenceValuesBinding
  403. with domain.l1.DefaultIntegerRangeValues
  404. with domain.l1.DefaultLongValues
  405. with domain.l1.ConcretePrimitiveValuesConversions
  406. with domain.l1.LongValuesShiftOperators
  407. with domain.TheProject
  408. with domain.TheMethod
  409. with domain.RecordDefUse
  410. def isGetter[S](method: Method)(implicit theProject: Project[S]): Boolean = {
  411. if (!method.isPublic || method.returnType.isVoidType || method.body.isEmpty ||
  412. !method.body.get.instructions.exists { i i.isInstanceOf[FieldReadAccess] }) {
  413. return false
  414. }
  415. val instructions = method.body.get.foldLeft(Map.empty[PC, Instruction])((m, pc, i) m + ((pc, i)))
  416. val result = BaseAI(method, new AnalysisDomain(theProject, method))
  417. val returns = instructions.filter(i i._2.isInstanceOf[ReturnValueInstruction])
  418. returns.forall(r result.domain.operandOrigin(r._1, 0).forall { u
  419. instructions.contains(u) && (instructions(u).isInstanceOf[FieldReadAccess] ||
  420. instructions(u).isInstanceOf[ArrayLoadInstruction] ||
  421. instructions(u).isInstanceOf[LoadConstantInstruction[_]])
  422. })
  423. }
  424. def isSetter[S](method: Method)(implicit theProject: Project[S]): Boolean = {
  425. if (!method.isPublic || !method.returnType.isVoidType ||
  426. method.descriptor.parametersCount == 0 || method.body.isEmpty ||
  427. (method.body.isDefined && method.body.isEmpty) ||
  428. !method.body.get.instructions.exists { i i.isInstanceOf[FieldWriteAccess] }) {
  429. return false
  430. }
  431. val instructions = method.body.get.foldLeft(Map.empty[PC, Instruction])((m, pc, i) m + ((pc, i)))
  432. val result = BaseAI(method, new AnalysisDomain(theProject, method))
  433. val puts = instructions.filter(i i._2.isInstanceOf[FieldWriteAccess])
  434. puts.forall(p (p._2.isInstanceOf[PUTSTATIC] &&
  435. result.domain.operandOrigin(p._1, 0).forall { x
  436. x < 0 || (instructions.contains(x) &&
  437. instructions(x).isInstanceOf[LoadConstantInstruction[_]])
  438. }) ||
  439. (p._2.isInstanceOf[PUTFIELD] &&
  440. result.domain.operandOrigin(p._1, 1).forall { x
  441. x < 0 || (instructions.contains(x) &&
  442. instructions(x).isInstanceOf[LoadConstantInstruction[_]])
  443. }))
  444. }
  445. }