org.sonar.java.se.MethodYield.java Source code

Java tutorial

Introduction

Here is the source code for org.sonar.java.se.MethodYield.java

Source

/*
 * SonarQube Java
 * Copyright (C) 2012-2016 SonarSource SA
 * mailto:contact AT sonarsource DOT com
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 3 of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */
package org.sonar.java.se;

import com.google.common.collect.Lists;
import org.apache.commons.lang.builder.EqualsBuilder;
import org.apache.commons.lang.builder.HashCodeBuilder;
import org.sonar.java.se.constraint.BooleanConstraint;
import org.sonar.java.se.constraint.Constraint;
import org.sonar.java.se.constraint.ObjectConstraint;
import org.sonar.java.se.symbolicvalues.SymbolicValue;
import org.sonar.plugins.java.api.semantic.Type;

import javax.annotation.CheckForNull;
import javax.annotation.Nullable;
import java.util.Arrays;
import java.util.Collection;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.function.Supplier;
import java.util.stream.Stream;

public class MethodYield {
    private final boolean varArgs;
    Constraint[] parametersConstraints;
    int resultIndex;
    @Nullable
    Constraint resultConstraint;
    @Nullable
    Type exceptionType;
    boolean exception;

    public MethodYield(int arity, boolean varArgs) {
        this.parametersConstraints = new Constraint[arity];
        this.varArgs = varArgs;
        this.resultIndex = -1;
        this.resultConstraint = null;
        this.exception = false;
        this.exceptionType = null;
    }

    @Override
    public String toString() {
        return String.format("{params: %s, result: %s (%d), exceptional: %b%s}",
                Arrays.toString(parametersConstraints), resultConstraint, resultIndex, exception,
                exceptionType == null ? "" : (" (" + exceptionType + ")"));
    }

    public Stream<ProgramState> statesAfterInvocation(List<SymbolicValue> invocationArguments,
            List<Type> invocationTypes, ProgramState programState, Supplier<SymbolicValue> svSupplier) {
        Set<ProgramState> results = new LinkedHashSet<>();
        for (int index = 0; index < invocationArguments.size(); index++) {
            Constraint constraint = getConstraint(index, invocationTypes);
            if (constraint == null) {
                // no constraint on this parameter, let's try next one.
                continue;
            }

            SymbolicValue invokedArg = invocationArguments.get(index);
            Set<ProgramState> programStates = programStatesForConstraint(
                    results.isEmpty() ? Lists.newArrayList(programState) : results, invokedArg, constraint);
            if (programStates.isEmpty()) {
                // constraint can't be satisfied, no need to process things further, this yield is not applicable.
                // TODO there might be some issue to report in this case.
                return Stream.empty();
            }
            results = programStates;
        }

        // resulting program states can be empty if all constraints on params are null or if method has no arguments.
        // That means that this yield is still possible and we need to stack a returned SV with its eventual constraints.
        if (results.isEmpty()) {
            results.add(programState);
        }

        // applied all constraints from parameters, stack return value
        SymbolicValue sv;
        if (resultIndex < 0) {
            sv = svSupplier.get();
        } else {
            // returned SV is the same as one of the arguments.
            sv = invocationArguments.get(resultIndex);
        }
        Stream<ProgramState> stateStream = results.stream().map(s -> s.stackValue(sv));
        if (resultConstraint != null) {
            stateStream = stateStream.map(s -> s.addConstraint(sv, resultConstraint));
        }
        return stateStream.distinct();
    }

    @CheckForNull
    private Constraint getConstraint(int index, List<Type> invocationTypes) {
        if (!varArgs || applicableOnVarArgs(index, invocationTypes)) {
            return parametersConstraints[index];
        }
        return null;
    }

    /**
     * For varArgs methods, only apply the constraint on single array parameter, in order to not 
     * wrongly apply it on all the elements of the array.
     */
    private boolean applicableOnVarArgs(int index, List<Type> types) {
        if (index < parametersConstraints.length - 1) {
            // not the varArg argument
            return true;
        }
        if (parametersConstraints.length != types.size()) {
            // more than one element in the variadic part
            return false;
        }
        Type argumentType = types.get(index);
        return argumentType.isArray() || argumentType.is("<nulltype>");
    }

    private static Set<ProgramState> programStatesForConstraint(Collection<ProgramState> states,
            SymbolicValue invokedArg, Constraint constraint) {
        Set<ProgramState> programStates = new LinkedHashSet<>();
        if (constraint instanceof ObjectConstraint) {
            ObjectConstraint objectConstraint = (ObjectConstraint) constraint;
            states.forEach(state -> programStates.addAll(invokedArg.setConstraint(state, objectConstraint)));
        } else if (constraint instanceof BooleanConstraint) {
            BooleanConstraint booleanConstraint = (BooleanConstraint) constraint;
            states.forEach(state -> programStates.addAll(invokedArg.setConstraint(state, booleanConstraint)));
        }
        return programStates;
    }

    @Override
    public int hashCode() {
        return new HashCodeBuilder(7, 1291).append(parametersConstraints).append(varArgs).append(resultIndex)
                .append(resultIndex).append(resultConstraint).append(exception).append(exceptionType).hashCode();
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }
        if (obj == null || getClass() != obj.getClass()) {
            return false;
        }
        MethodYield other = (MethodYield) obj;
        return new EqualsBuilder().append(parametersConstraints, other.parametersConstraints)
                .append(varArgs, other.varArgs).append(resultIndex, other.resultIndex)
                .append(resultConstraint, other.resultConstraint).append(exception, other.exception)
                .append(exceptionType, other.exceptionType).isEquals();
    }
}