/*
* 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();
}
}