All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.opalj.hermes.queries.MicroPatterns.scala Maven / Gradle / Ivy

The newest version!
/* BSD 2-Clause License:
 * Copyright (c) 2009 - 2017
 * Software Technology Group
 * Department of Computer Science
 * Technische Universität Darmstadt
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 *  - Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer.
 *  - Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */
package org.opalj
package hermes
package queries

import org.opalj.br.ClassFile
import org.opalj.br.Method
import org.opalj.br.Field
import org.opalj.br.ObjectType
import org.opalj.br.MethodDescriptor
import org.opalj.br.analyses.SomeProject
import org.opalj.br.analyses.FieldAccessInformation
import org.opalj.br.analyses.FieldAccessInformationKey
import org.opalj.br.analyses.Project
import org.opalj.br.instructions.ArrayLoadInstruction
import org.opalj.br.instructions.FieldReadAccess
import org.opalj.br.instructions.FieldWriteAccess
import org.opalj.br.instructions.LoadConstantInstruction
import org.opalj.br.instructions.MethodInvocationInstruction
import org.opalj.br.instructions.PUTFIELD
import org.opalj.br.instructions.PUTSTATIC
import org.opalj.br.instructions.ReturnValueInstruction
import org.opalj.br.instructions.VirtualMethodInvocationInstruction
import org.opalj.ai.BaseAI
import org.opalj.ai.CorrelationalDomain
import org.opalj.ai.domain
import org.opalj.br.LongType

/**
 * Counts which kinds of micro patterns are actually available.
 *
 * @author Leonid Glanz
 */
object MicroPatterns extends FeatureQuery {

    override val featureIDs: List[String] = {
        List(
            /*0*/ "Designator",
            /*1*/ "Taxonomy",
            /*2*/ "Joiner",
            /*3*/ "Pool",
            /*4*/ "Function Pointer",
            /*5*/ "Function Object",
            /*6*/ "Cobol Like",
            /*7*/ "Stateless",
            /*8*/ "Common State",
            /*9*/ "Immutable",
            /*10*/ "Restricted Creation",
            /*11*/ "Sampler",
            /*12*/ "Box",
            /*13*/ "Compound Box",
            /*14*/ "Canopy",
            /*15*/ "Record",
            /*16*/ "Data Manager",
            /*17*/ "Sink",
            /*18*/ "Outline",
            /*19*/ "Trait",
            /*20*/ "State Machine",
            /*21*/ "Pure Type",
            /*22*/ "Augmented Type",
            /*23*/ "Pseudo Class",
            /*24*/ "Implementor",
            /*25*/ "Overrider",
            /*26*/ "Extender"
        )
    }

    override def apply[S](
        projectConfiguration: ProjectConfiguration,
        project:              Project[S],
        rawClassFiles:        Traversable[(org.opalj.da.ClassFile, S)]
    ): TraversableOnce[Feature[S]] = {
        implicit val theProject = project

        val fa = project.get(FieldAccessInformationKey)

        val microPatternLocations = Array.fill(featureIDs.size)(new LocationsContainer[S])
        for {
            (classFile, source) ← project.projectClassFilesWithSources
            if !isInterrupted()
        } {
            val location = ClassFileLocation(source, classFile)

            if (isDesignator(classFile)) microPatternLocations(0) += location
            if (isTaxonomy(classFile)) microPatternLocations(1) += location
            if (isJoiner(classFile)) microPatternLocations(2) += location
            if (isPool(classFile)) microPatternLocations(3) += location
            if (isFunctionPointer(classFile)) microPatternLocations(4) += location
            if (isFunctionObject(classFile)) microPatternLocations(5) += location
            if (isCobolLike(classFile)) microPatternLocations(6) += location
            if (isStateless(classFile)) microPatternLocations(7) += location
            if (isCommonState(classFile)) microPatternLocations(8) += location
            if (isImmutable(classFile, fa)) microPatternLocations(9) += location
            if (isRestrictedCreation(classFile)) microPatternLocations(10) += location
            if (isSampler(classFile)) microPatternLocations(11) += location
            if (isBox(classFile, fa)) microPatternLocations(12) += location
            if (isCompoundBox(classFile, fa)) microPatternLocations(13) += location
            if (isCanopy(classFile, fa)) microPatternLocations(14) += location
            if (isRecord(classFile)) microPatternLocations(15) += location
            if (isDataManager(classFile)) microPatternLocations(16) += location
            if (isSink(classFile)) microPatternLocations(17) += location
            if (isOutline(classFile)) microPatternLocations(18) += location
            if (isTrait(classFile)) microPatternLocations(19) += location
            if (isStateMachine(classFile)) microPatternLocations(20) += location
            if (isPureType(classFile)) microPatternLocations(21) += location
            if (isAugmentedType(classFile)) microPatternLocations(22) += location
            if (isPseudoClass(classFile)) microPatternLocations(23) += location
            if (isImplementor(classFile, project)) microPatternLocations(24) += location
            if (isOverrider(classFile, project)) microPatternLocations(25) += location
            if (isExtender(classFile)) microPatternLocations(26) += location
        }
        for { (featureID, featureIDIndex) ← featureIDs.iterator.zipWithIndex } yield {
            Feature[S](featureID, microPatternLocations(featureIDIndex))
        }
    }

    def hasExplicitSuperType(cl: ClassFile): Boolean = cl.superclassType.exists(_ ne ObjectType.Object)

    /**
     * [From the paper] Thus, a Designator micro pattern is an interface which does not
     *  declare any methods, does not define any static fields or methods, and does not
     *  inherit such members from any of its superinterfaces.
     *
     *  A class can also be Designator if its definition, as well as the definitions of
     *  all of its ancestors (other than Object), are empty.
     *
     * A class can also be Designator if its definition, as well as the definitions of
     * all of its ancestors (other than Object), are empty.
     */
    def isDesignator(cl: ClassFile)(implicit project: SomeProject): Boolean = {
        if (cl.thisType == ObjectType.Object)
            return false;

        // IMPROVE Cache the results of super interfaces to avoid recomputations or compute it top-down starting with top-level interfaces.

        def isDesignatorType(ot: ObjectType): Boolean = {
            project.classFile(ot).exists(this.isDesignator)
        }

        cl.fields.isEmpty && {
            if (cl.isInterfaceDeclaration)
                cl.methods.isEmpty && cl.interfaceTypes.forall(isDesignatorType)
            else
                // we have to filter the always present default constructor (compiler generated,
                // if not user defined)
                cl.methods.size == 1 && cl.methods.head.descriptor == MethodDescriptor.NoArgsAndReturnVoid &&
                    cl.interfaceTypes.forall(isDesignatorType) &&
                    isDesignatorType(cl.superclassType.get)
        }

    }

    /**
     * [From the paper:] An empty interface which extends a single interface is
     * called a Taxonomy, since it is included, in the subtyping sense, in its parent,
     * but otherwise identical to it.
     *
     * There are also classes which are Taxonomy. Such a class must similarly be empty,
     * i.e., add no fields nor methods to its parent. Since constructors are not inherited,
     * an empty class may contain constructors. A Taxonomy class may not implement any interfaces.
     */
    def isTaxonomy(cl: ClassFile): Boolean = {
        cl.fields.isEmpty && {
            if (cl.isInterfaceDeclaration) {
                cl.interfaceTypes.size == 1 && cl.methods.isEmpty
            } else {
                cl.thisType != ObjectType.Object /*this test is not necessary, but is fast */ &&
                    cl.interfaceTypes.isEmpty &&
                    cl.methods.forall(_.isInitializer)
            }
        }
    }

    /**
     * [From the paper:] An empty interface which extends more than one interface is called a
     * Joiner, since in effect, it joins together the sets of members of its parents.
     *
     * An empty class which implements one or more interfaces is also a Joiner.
     * ''Here, empty means that the we can have constructors and (optionally) a serialVersionUID
     * field.''
     */
    def isJoiner(cl: ClassFile): Boolean = {
        if (cl.isInterfaceDeclaration) {
            cl.interfaceTypes.size > 1 && cl.fields.isEmpty && cl.methods.isEmpty
        } else {
            cl.interfaceTypes.nonEmpty &&
                cl.methods.forall(m ⇒ m.isInitializer) && (
                    cl.fields match {
                        case Seq() | Seq(Field(_, "serialVersionUID", LongType)) ⇒ true
                        case _                                                   ⇒ false
                    }
                )
        }
    }

    /**
     * [From the paper:] The most degenerate classes are those which have neither state
     * nor behavior. Such a class is distinguished by the requirement that it declares
     * no instance fields. Moreover, all of its declared static fields must be final.
     * Another requirement is that the class has no methods (other than those inherited
     * from Object, or automatically generated constructors).
     */
    def isPool(cl: ClassFile): Boolean = {
        cl.fields.nonEmpty && cl.fields.forall(f ⇒ f.isFinal && f.isStatic) &&
            // We also (have to) accept a static initializer, because that one will
            // initialize the final static fields!
            cl.methods.forall(m ⇒ m.isInitializer && m.descriptor.parametersCount == 0)
    }

    private final val javaLangObjectMethods: Set[String] = Set(
        "hashCode", "equals",
        "notify", "notifyAll", "wait",
        "getClass", "clone", "toString", "finalize"
    )

    def isObjectMethod(method: Method): Boolean = {
        // TODO This just checks the name, why don't we check the full signature?
        javaLangObjectMethods.contains(method.name)
    }

    def isFunctionPointer(cl: ClassFile): Boolean = {
        !cl.isInterfaceDeclaration && !cl.isAbstract && cl.methods.count { m ⇒
            !isInitMethod(m)
        } == 1 && cl.methods.count { m ⇒
            m.isPublic
        } == 1 && !cl.methods.exists(m ⇒ m.isStatic &&
            !m.isStaticInitializer) && cl.fields.isEmpty
    }

    def isFunctionObject(cl: ClassFile): Boolean = {
        cl.fields.nonEmpty &&
            cl.fields.forall { f ⇒ !f.isStatic } &&
            cl.methods.count { m ⇒ !isInitMethod(m) && m.isPublic } == 1 &&
            !cl.methods.filter(m ⇒ !isInitMethod(m)).exists(m ⇒ m.isStatic)
    }

    def isCobolLike(cl: ClassFile): Boolean = {
        !cl.methods.exists { m ⇒
            !isInitMethod(m)
        } && cl.methods.count { m ⇒
            !isInitMethod(m) &&
                m.isStatic
        } == 1 &&
            !cl.fields.exists { f ⇒ !f.isStatic } && cl.fields.exists(f ⇒ f.isStatic)
    }

    def isStateless(cl: ClassFile): Boolean = {
        !cl.isInterfaceDeclaration && !cl.isAbstract &&
            !cl.fields.exists { f ⇒ !(f.isFinal && f.isStatic) } &&
            cl.methods.count(m ⇒ !isInitMethod(m) && !isObjectMethod(m)) > 1
    }

    def isCommonState(cl: ClassFile): Boolean = {
        !cl.isInterfaceDeclaration &&
            cl.fields.nonEmpty && cl.fields.forall { f ⇒ f.isStatic } &&
            cl.fields.exists { f ⇒ !f.isFinal }
    }

    def isImmutable(cl: ClassFile, fa: FieldAccessInformation): Boolean = {
        !cl.isInterfaceDeclaration &&
            !cl.isAbstract && cl.fields.count { f ⇒ !f.isStatic } > 1 &&
            cl.fields.forall(f ⇒ f.isPrivate && !f.isStatic) &&
            cl.fields.forall { f ⇒
                !fa.allWriteAccesses.contains(f) ||
                    fa.allWriteAccesses(f).forall(p ⇒ isInitMethod(p._1))
            }
    }

    def isRestrictedCreation(cl: ClassFile): Boolean = {
        !cl.isInterfaceDeclaration &&
            cl.fields.exists { f ⇒ f.isStatic && !f.isFinal && f.fieldType == cl.thisType } &&
            cl.methods.filter { m ⇒ m.isConstructor }.forall { m ⇒ m.isPrivate }
    }

    def isSampler(cl: ClassFile): Boolean = {
        !cl.isInterfaceDeclaration &&
            cl.methods.filter { m ⇒ m.isConstructor }.exists { m ⇒ m.isPublic } &&
            cl.fields.exists { f ⇒
                f.isStatic &&
                    f.fieldType.toJava.equals(cl.thisType.toJava)
            }
    }

    def isBox(cl: ClassFile, fa: FieldAccessInformation): Boolean = {
        !cl.isInterfaceDeclaration &&
            cl.fields.count { f ⇒ !f.isStatic } == 1 &&
            cl.fields.count { f ⇒ !f.isFinal } == 1 &&
            cl.fields.exists(f ⇒ fa.allWriteAccesses.contains(f) &&
                fa.allWriteAccesses(f).exists(t ⇒ cl.methods.contains(t._1)))
    }

    def isCompoundBox(cl: ClassFile, fa: FieldAccessInformation): Boolean = {
        !cl.isInterfaceDeclaration &&
            cl.fields.count(f ⇒ f.fieldType.isReferenceType && !f.isStatic &&
                !f.isFinal && fa.allWriteAccesses.contains(f) &&
                fa.allWriteAccesses(f).exists(t ⇒ cl.methods.contains(t._1))) == 1 &&
            cl.fields.count(f ⇒ !f.isStatic && !f.fieldType.isReferenceType) + 1 == cl.fields.size
    }

    def isCanopy(cl: ClassFile, fa: FieldAccessInformation): Boolean = {
        !cl.isInterfaceDeclaration &&
            cl.fields.count { f ⇒ !f.isStatic } == 1 &&
            cl.fields.count { f ⇒ !f.isStatic && !f.isPublic } == 1 &&
            cl.fields.exists { f ⇒
                !f.isStatic && fa.allWriteAccesses.contains(f) &&
                    fa.allWriteAccesses(f).forall(p ⇒ isInitMethod(p._1))
            }
    }

    def isRecord(cl: ClassFile): Boolean = {
        !cl.isInterfaceDeclaration && cl.fields.nonEmpty &&
            cl.fields.forall { f ⇒ f.isPublic } && cl.fields.exists(f ⇒ !f.isStatic) && cl.methods.forall(m ⇒ isInitMethod(m) || isObjectMethod(m))
    }

    def isSink(cl: ClassFile): Boolean = {
        !cl.isInterfaceDeclaration &&
            cl.methods.exists { m ⇒ !isInitMethod(m) } &&
            cl.methods.forall { m ⇒
                m.body.isEmpty ||
                    m.body.get.instructions.filter(i ⇒ i.isInstanceOf[MethodInvocationInstruction]).forall { i ⇒
                        !i.asInstanceOf[MethodInvocationInstruction].declaringClass.isObjectType ||
                            i.asInstanceOf[MethodInvocationInstruction].declaringClass.asObjectType.equals(cl.thisType)
                    }
            }
    }

    def isOutline(cl: ClassFile): Boolean = {
        !cl.isInterfaceDeclaration && cl.isAbstract && cl.methods.count { m ⇒
            m.body.isDefined &&
                m.body.get.instructions.exists { i ⇒
                    i.isInstanceOf[VirtualMethodInvocationInstruction] &&
                        i.asInstanceOf[VirtualMethodInvocationInstruction].declaringClass.equals(cl.thisType) &&
                        cl.methods.filter { x ⇒ x.isAbstract }.exists { x ⇒
                            x.name.equals(i.asInstanceOf[VirtualMethodInvocationInstruction].name) &&
                                x.descriptor.equals(i.asInstanceOf[VirtualMethodInvocationInstruction].methodDescriptor)
                        }
                }
        } > 1
    }

    def isTrait(cl: ClassFile): Boolean = {
        !cl.isInterfaceDeclaration && cl.isAbstract &&
            cl.fields.isEmpty &&
            cl.methods.exists(m ⇒ m.isAbstract)
    }

    def isStateMachine(cl: ClassFile): Boolean = {
        cl.methods.count(m ⇒ !isInitMethod(m)) > 1 &&
            cl.fields.nonEmpty &&
            cl.methods.forall { m ⇒ m.descriptor.parametersCount == 0 }
    }

    def isPureType(cl: ClassFile): Boolean = {
        (
            (
                cl.isAbstract && cl.methods.nonEmpty &&
                cl.methods.forall { m ⇒ m.isAbstract && !m.isStatic }
            ) ||
                (
                    cl.isInterfaceDeclaration && cl.methods.nonEmpty && cl.methods.forall { m ⇒ !m.isStatic }
                )
        ) &&
                    cl.fields.isEmpty
    }

    def isAugmentedType(cl: ClassFile): Boolean = {
        !cl.isInterfaceDeclaration && cl.isAbstract &&
            cl.methods.forall { m ⇒ m.isAbstract } && cl.fields.size >= 3 &&
            cl.fields.forall { f ⇒ f.isFinal && f.isStatic } &&
            cl.fields.map { f ⇒ f.fieldType }.toSet.size == 1
    }

    def isPseudoClass(cl: ClassFile): Boolean = {
        !cl.isInterfaceDeclaration &&
            cl.fields.forall { f ⇒ f.isStatic } && cl.methods.nonEmpty &&
            cl.methods.forall { m ⇒ m.isAbstract || m.isStatic }
    }

    def isImplementor[S](cl: ClassFile, theProject: Project[S]): Boolean = {
        !cl.isInterfaceDeclaration &&
            !cl.isAbstract && cl.methods.exists { m ⇒
                m.isPublic &&
                    !isInitMethod(m)
            } && cl.methods.forall { m ⇒
                isInitMethod(m) || !m.isPublic ||
                    (theProject.resolveMethodReference(cl.thisType, m.name, m.descriptor) match {
                        case Some(a) ⇒ (a.isAbstract || a.body.isEmpty) && (
                            (hasExplicitSuperType(cl) && a.classFile != null &&
                                a.classFile.thisType == cl.superclassType.get) ||
                                cl.interfaceTypes.exists(it ⇒ a.classFile != null && a.classFile.thisType == it)
                        )
                        case None ⇒ false
                    })
            }
    }

    def isInitMethod(method: Method): Boolean = method.isInitializer

    def isOverrider[S](cl: ClassFile, theProject: Project[S]): Boolean = {
        !cl.isInterfaceDeclaration && !cl.isAbstract &&
            cl.methods.exists { m ⇒
                !isInitMethod(m)
            } && cl.methods.forall { m ⇒
                m.isInitializer ||
                    (theProject.resolveMethodReference(cl.thisType, m.name, m.descriptor) match {
                        case Some(a) ⇒ (!a.isAbstract && a.body.isDefined && m.body.nonEmpty) && (
                            (hasExplicitSuperType(cl) && a.classFile != null &&
                                a.classFile.thisType == cl.superclassType.get) ||
                                cl.interfaceTypes.exists(it ⇒ a.classFile != null && a.classFile.thisType == it)
                        )
                        case None ⇒ false
                    })
            }
    }

    def isExtender[S](cl: ClassFile)(implicit theProject: Project[S]): Boolean = {
        !cl.isInterfaceDeclaration &&
            cl.methods.exists(m ⇒ !isInitMethod(m)) && hasExplicitSuperType(cl) && cl.methods.forall { m ⇒
                isInitMethod(m) ||
                    theProject.resolveMethodReference(cl.thisType, m.name, m.descriptor).isEmpty
            }
    }

    def isDataManager[S](cl: ClassFile)(implicit theProject: Project[S]): Boolean = {
        !cl.isInterfaceDeclaration &&
            !cl.isAbstract &&
            cl.fields.nonEmpty &&
            cl.methods.count(m ⇒ !isInitMethod(m) && !isObjectMethod(m)) > 1 &&
            cl.methods.filter(m ⇒ !isInitMethod(m) && !isObjectMethod(m)).forall { m ⇒
                isSetter(m) || isGetter(m)
            }
    }

    class AnalysisDomain[S](val project: Project[S], val method: Method)
        extends CorrelationalDomain
        with domain.DefaultHandlingOfMethodResults
        with domain.IgnoreSynchronization
        with domain.ThrowAllPotentialExceptionsConfiguration
        with domain.l0.DefaultTypeLevelFloatValues
        with domain.l0.DefaultTypeLevelDoubleValues
        with domain.l0.TypeLevelFieldAccessInstructions
        with domain.l0.TypeLevelInvokeInstructions
        with domain.l1.DefaultReferenceValuesBinding
        with domain.l1.DefaultIntegerRangeValues
        with domain.l1.DefaultLongValues
        with domain.l1.ConcretePrimitiveValuesConversions
        with domain.l1.LongValuesShiftOperators
        with domain.TheProject
        with domain.TheMethod
        with domain.RecordDefUse

    def isGetter[S](method: Method)(implicit theProject: Project[S]): Boolean = {
        if (!method.isPublic || method.returnType.isVoidType || method.body.isEmpty ||
            !method.body.get.instructions.exists { i ⇒ i.isInstanceOf[FieldReadAccess] }) {
            return false
        }

        val instructions = method.body.get.associateWithIndex().toMap
        val result = BaseAI(method, new AnalysisDomain(theProject, method))
        val returns = instructions.filter(i ⇒ i._2.isInstanceOf[ReturnValueInstruction])

        returns.forall(r ⇒ result.domain.operandOrigin(r._1, 0).forall { u ⇒
            instructions.contains(u) && (instructions(u).isInstanceOf[FieldReadAccess] ||
                instructions(u).isInstanceOf[ArrayLoadInstruction] ||
                instructions(u).isInstanceOf[LoadConstantInstruction[_]])
        })
    }

    def isSetter[S](method: Method)(implicit theProject: Project[S]): Boolean = {
        if (!method.isPublic || !method.returnType.isVoidType ||
            method.descriptor.parametersCount == 0 || method.body.isEmpty ||
            (method.body.isDefined && method.body.isEmpty) ||
            !method.body.get.instructions.exists { i ⇒ i.isInstanceOf[FieldWriteAccess] }) {
            return false
        }

        val instructions = method.body.get.associateWithIndex().toMap
        val result = BaseAI(method, new AnalysisDomain(theProject, method))
        val puts = instructions.filter(i ⇒ i._2.isInstanceOf[FieldWriteAccess])

        puts.forall(p ⇒ (p._2.isInstanceOf[PUTSTATIC] &&
            result.domain.operandOrigin(p._1, 0).forall { x ⇒
                x < 0 || (instructions.contains(x) &&
                    instructions(x).isInstanceOf[LoadConstantInstruction[_]])
            }) ||
            (p._2.isInstanceOf[PUTFIELD] &&
                result.domain.operandOrigin(p._1, 1).forall { x ⇒
                    x < 0 || (instructions.contains(x) &&
                        instructions(x).isInstanceOf[LoadConstantInstruction[_]])
                }))
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy