Commit 9c08cc9b authored by Carlos Galindo's avatar Carlos Galindo
Browse files

Connect all exception return nodes to some exception exit node

Previously, those that did not perfectly match were discarded and left unconnected.
parent eeac3bbc
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -27,7 +27,7 @@
        <dependency>
            <groupId>tfm</groupId>
            <artifactId>sdg-core</artifactId>
            <version>1.0.1</version>
            <version>1.0.2</version>
            <scope>compile</scope>
        </dependency>
    </dependencies>
+1 −1
Original line number Diff line number Diff line
@@ -6,7 +6,7 @@

    <groupId>tfm</groupId>
    <artifactId>sdg-core</artifactId>
    <version>1.0.1</version>
    <version>1.0.2</version>

    <properties>
        <maven.compiler.source>11</maven.compiler.source>
+78 −9
Original line number Diff line number Diff line
@@ -2,13 +2,15 @@ package tfm.graphs.exceptionsensitive;

import com.github.javaparser.ast.body.MethodDeclaration;
import com.github.javaparser.ast.expr.MethodCallExpr;
import com.github.javaparser.ast.type.ReferenceType;
import com.github.javaparser.resolution.declarations.ResolvedMethodDeclaration;
import com.github.javaparser.resolution.types.ResolvedReferenceType;
import com.github.javaparser.resolution.types.ResolvedType;
import tfm.graphs.sdg.MethodCallReplacerVisitor;
import tfm.nodes.*;
import tfm.utils.Context;

import java.util.Set;
import java.util.*;
import java.util.stream.Collectors;

public class ExceptionSensitiveMethodCallReplacerVisitor extends MethodCallReplacerVisitor {
@@ -19,11 +21,18 @@ public class ExceptionSensitiveMethodCallReplacerVisitor extends MethodCallRepla
    @Override
    public void visit(MethodCallExpr methodCallExpr, Context context) {
        if (methodCallExpr.resolve().getNumberOfSpecifiedExceptions() > 0)
            connectExitToReturn(methodCallExpr);
            handleExceptionReturnArcs(methodCallExpr);
        super.visit(methodCallExpr, context);
    }

    protected void connectExitToReturn(MethodCallExpr call) {
    /** Creates the following connections:
     * <ul>
     *     <li>{@link ExceptionExitNode} to {@link ExceptionReturnNode} with a ratio of (* to 1), n per method</li>
     *     <li>{@link NormalExitNode} to {@link NormalReturnNode} with a ratio of (1 to 1), 1 per method</li>
     * </ul>
     * @param call The method call to be connected to its method declaration.
     */
    protected void handleExceptionReturnArcs(MethodCallExpr call) {
        Set<SyntheticNode<?>> synthNodes = sdg.vertexSet().stream()
                .filter(SyntheticNode.class::isInstance)
                .map(n -> (SyntheticNode<?>) n)
@@ -31,6 +40,12 @@ public class ExceptionSensitiveMethodCallReplacerVisitor extends MethodCallRepla
        ResolvedMethodDeclaration resolvedDecl = call.resolve();
        MethodDeclaration decl = resolvedDecl.toAst().orElseThrow();

        connectNormalNodes(synthNodes, call, decl);
        connectExceptionNodes(synthNodes, call, decl);
    }

    /** Connects normal exit nodes to their corresponding return node. */
    protected void connectNormalNodes(Set<SyntheticNode<?>> synthNodes, MethodCallExpr call, MethodDeclaration decl) {
        ReturnNode normalReturn = (ReturnNode) synthNodes.stream()
                .filter(NormalReturnNode.class::isInstance)
                .filter(n -> n.getAstNode() == call)
@@ -40,21 +55,75 @@ public class ExceptionSensitiveMethodCallReplacerVisitor extends MethodCallRepla
                .filter(n -> n.getAstNode() == decl)
                .findAny().orElseThrow();
        ((ESSDG) sdg).addReturnArc(normalExit, normalReturn);
    }

        for (ResolvedType type : resolvedDecl.getSpecifiedExceptions()) {
            ReturnNode exceptionReturn = synthNodes.stream()
    /**
     * Connects exception exit nodes to their corresponding return node,
     * taking into account that return nodes are generated from the 'throws' list
     * in the method declaration and exit nodes are generated from the exception sources
     * that appear in the method. This creates a mismatch that is solved in {@link #connectRemainingExceptionNodes(Map, Set)}
     */
    protected void connectExceptionNodes(Set<SyntheticNode<?>> synthNodes, MethodCallExpr call, MethodDeclaration decl) {
        Map<ResolvedType, ExceptionReturnNode> exceptionReturnMap = new HashMap<>();
        Set<ExceptionExitNode> eeNodes = synthNodes.stream()
                .filter(ExceptionExitNode.class::isInstance)
                .map(ExceptionExitNode.class::cast)
                .filter(n -> n.getAstNode() == decl)
                .collect(Collectors.toSet());
        for (ReferenceType rType : decl.getThrownExceptions()) {
            ResolvedType type = rType.resolve();
            ExceptionReturnNode exceptionReturn = synthNodes.stream()
                    .filter(ExceptionReturnNode.class::isInstance)
                    .map(ExceptionReturnNode.class::cast)
                    .filter(n -> n.getAstNode() == call)
                    .filter(n -> n.getExceptionType().equals(type))
                    .findAny().orElseThrow();
            ExitNode exceptionExit = synthNodes.stream()
                    .filter(ExceptionExitNode.class::isInstance)
                    .map(ExceptionExitNode.class::cast)
                    .filter(n -> n.getAstNode() == decl)
            ExceptionExitNode exceptionExit = eeNodes.stream()
                    .filter(n -> n.getExceptionType().equals(type))
                    .findAny().orElseThrow();
            eeNodes.remove(exceptionExit);
            exceptionReturnMap.put(type, exceptionReturn);
            ((ESSDG) sdg).addReturnArc(exceptionExit, exceptionReturn);
        }

        connectRemainingExceptionNodes(exceptionReturnMap, eeNodes);
    }

    /** Connects the remaining exception exit nodes to their closest exception return node. */
    protected void connectRemainingExceptionNodes(Map<ResolvedType, ExceptionReturnNode> exceptionReturnMap, Set<ExceptionExitNode> eeNodes) {
        boolean hasThrowable = resolvedTypeSetContains(exceptionReturnMap.keySet(), "java.lang.Throwable");
        boolean hasException = resolvedTypeSetContains(exceptionReturnMap.keySet(), "java.lang.Exception");

        eeFor: for (ExceptionExitNode ee : eeNodes) {
            List<ResolvedReferenceType> typeList = List.of(ee.getExceptionType().asReferenceType());
            while (!typeList.isEmpty()) {
                List<ResolvedReferenceType> newTypeList = new LinkedList<>();
                for (ResolvedReferenceType type : typeList) {
                    if (exceptionReturnMap.containsKey(type)) {
                        ((ESSDG) sdg).addReturnArc(ee, exceptionReturnMap.get(type));
                        continue eeFor;
                    }
                    // Skip RuntimeException, unless Throwable or Exception are present as ER nodes
                    if (type.getQualifiedName().equals("java.lang.RuntimeException") && !hasThrowable && !hasException)
                        continue;
                    // Skip Error, unless Throwable is present as EE node
                    if (type.getQualifiedName().equals("java.lang.Error") && !hasThrowable)
                        continue;
                    // Object has no ancestors, the search has ended
                    if (!type.getQualifiedName().equals("java.lang.Object"))
                        newTypeList.addAll(type.asReferenceType().getDirectAncestors());
                }
                typeList = newTypeList;
            }
        }
    }

    /** Utility method. Finds whether or not a type can be found in a set of resolved types.
     * The full qualified name should be used (e.g. 'java.lang.Object' for Object). */
    protected static boolean resolvedTypeSetContains(Set<ResolvedType> set, String qualifiedType) {
        return set.stream()
                .map(ResolvedType::asReferenceType)
                .map(ResolvedReferenceType::getQualifiedName)
                .anyMatch(qualifiedType::equals);
    }
}