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

io.rivulet.internal.rerun.ReplacementImplBuilder Maven / Gradle / Ivy

The newest version!
package io.rivulet.internal.rerun;

import io.rivulet.converter.*;
import io.rivulet.internal.ProcessedSourceInfoTaintLabel;
import io.rivulet.internal.SourceInfoTaintLabel;
import io.rivulet.internal.Violation;

import java.util.*;

/* Generates ReplacementImpls based on supplied criteria. */
public class ReplacementImplBuilder implements ReplacementBuilder {

    // The replacementValue that will be used to replace the original value at the source. Must be non-null.
    private Object replacementValue;
    // Targeted baseSource of the label of values at the source that should be replaced. Must be non-null if isPayload is false.
    private String baseSource;
    // Targeted actualSourceClass of the label of values at the source that should be replaced. A null replacementValue indicates
    // that every actualSourceClass should be replaced.
    private String actualSourceClass;
    // Targeted sourceArgIndex of the label of values at the source that should be replaced. A replacementValue of ReplacementImpl.ANY_INT
    // indicates that every sourceArgIndex should be replaced.
    private int sourceArgIndex;
    // Whether the success of a replacement generated by this builder impacts the overall success of a rerun. If isPayload
    // is true and required is true, then a rerun configuration generated with this builder must contain at least one of
    // the replacements generated by this builder.
    private boolean required;
    // Whether this replacement should be bound to a tainted replacementValue that reached the sink during the original run. If this
    // builder's criteria is valid for more than one tainted replacementValue label, then a ReplacementImpl is generated for each
    // distinct qualifying label.
    private boolean isPayload;

    /* Constructs a new ReplacementImplBuilder with all of its fields in their respective accept any replacementValue states. */
    public ReplacementImplBuilder() {
        this.replacementValue = null;
        this.baseSource = actualSourceClass = null;
        this.sourceArgIndex = ReplacementImpl.ANY_INT;
        this.required = false;
        this.isPayload = false;
    }

    /* Sets replacementValue to the specified replacementValue. Returns this builder. */
    public ReplacementImplBuilder value(Object value) {
        this.replacementValue = value;
        return this;
    }

    /* Sets baseSource to the specified replacementValue. Returns this builder. */
    public ReplacementImplBuilder baseSource(String baseSource) {
        this.baseSource = baseSource;
        return this;
    }

    /* Sets actualSourceClass to the specified replacementValue. Returns this builder. */
    public ReplacementImplBuilder actualSourceClass(String actualSourceClass) {
        this.actualSourceClass = actualSourceClass;
        return this;
    }

    /* Sets sourceArgIndex to the specified replacementValue. Returns this builder. */
    public ReplacementImplBuilder sourceArgIndex(int sourceArgIndex) {
        this.sourceArgIndex = sourceArgIndex;
        return this;
    }

    /* Sets required to the specified replacementValue. Returns this builder. */
    public ReplacementImplBuilder required(boolean required) {
        this.required = required;
        return this;
    }

    /* Sets isPayload to the specified replacementValue. Returns this builder. */
    public ReplacementImplBuilder isPayload(boolean isPayload) {
        this.isPayload = isPayload;
        return this;
    }

    /* Getter for isPayload. */
    public boolean isPayload() {
        return isPayload;
    }

    /* Returns whether baseSource is non-null. */
    public boolean hasBaseSource() {
        return baseSource != null;
    }

    /* Returns whether or not at least one replacement generated from this builder must be included in a rerun configuration
     * for the configuration to be considered valid. */
    @Override
    public boolean isRequiredForGeneration() {
        return required && isPayload;
    }

    /* Throws a runtime exception if this builder specifies invalid replacement construction criteria. */
    public void validate() {
        if(replacementValue == null) {
            throw new RuntimeException("Replacements must specify a non-null replacement replacementValue.");
        } else if(baseSource == null && !isPayload) {
            throw new RuntimeException(("Non-payload replacements must specify a baseSource for the replacement to target."));
        } else if(sourceArgIndex < -1 && sourceArgIndex != ReplacementImpl.ANY_INT) {
            throw new RuntimeException(String.format("Replacement cannot target invalid source argument index: %d.", sourceArgIndex));
        }
    }

    /* Returns a set of ReplacementImpls containing all of the valid combinations of generators and, if isPayload is true,
     * label bindings. */
    @Override
    public LinkedHashSet build(Violation violation) {
        validate();
        LinkedHashSet replacements = new LinkedHashSet<>();
        for(SourceInfoTaintLabel label : violation.getAllLabels()) {
            replacements.addAll(generateReplacements(label));
        }
        return replacements;
    }

    /* Returns replacements created based on the specified label and this builder's information. */
    private LinkedHashSet generateReplacements(SourceInfoTaintLabel label) {
        LinkedHashSet replacements = new LinkedHashSet<>();
        if(meetsCriteria(label) || !isPayload) {
            List validConverters = ConversionUtils.getValidConverters(isPayload ? label.getBaseSource() : baseSource);
            for(ForcedTypeConverter converter : validConverters) {
                replacements.addAll(generateReplacements(label, converter));
            }
        }
        return replacements;
    }

    /* Returns replacements created based on the specified label and this builder's information that apply the specified
     * converter first when replacing. */
    private LinkedHashSet generateReplacements(SourceInfoTaintLabel label, ForcedTypeConverter baseConverter) {
        LinkedHashSet replacements = new LinkedHashSet<>();
        if(label instanceof ProcessedSourceInfoTaintLabel && (isPayload || producibleByTargetedSourceClass(label))) {
            // Add replacements with invocation information if it is available and applicable
            ProcessedSourceInfoTaintLabel processedLabel = (ProcessedSourceInfoTaintLabel) label;
            for(int id : processedLabel.getIndexInfoMap().keySet()) {
                // Make one rerun for each invocation id
                addReplacement(replacements, label, baseConverter, new int[]{id});
            }
            if(isPayload) {
                // Add replacements with index and invocation information
                for(int id : processedLabel.getIndexInfoMap().keySet()) {
                    int[] pairs = processedLabel.getIndexInfoMap().get(id).getRangesCopy();
                    for(int i = 0; i < pairs.length; i+=2) {
                        ForcedTypeConverter chainConverter = getChainedRangeConverter(baseConverter, label.getSourceValueClass(), pairs[i], pairs[i+1]);
                        if(chainConverter != null) {
                            // Make one rerun for each range for the id
                            addReplacement(replacements, label, chainConverter, new int[]{id});
                        }
                    }
                }
            }
        }
        if(replacements.isEmpty()) {
            // Add a replacement with no invocation or index information
            addReplacement(replacements, label, baseConverter, null);
        }
        return  replacements;
    }

    /* Adds a new replacement which uses the specified converter and targets the specified invocation ids to the specified
     * set if this is a non-payload builder or the specified converted can be used for this instance's replacement
     * value for the specified label. */
    private void addReplacement(Set replacements, SourceInfoTaintLabel label, ForcedTypeConverter converter, int[] invocationIDs) {
        if(!isPayload) {
            replacements.add(new ReplacementImpl(replacementValue, baseSource, actualSourceClass, sourceArgIndex, null, converter, required, invocationIDs));
        } else if(canConvert(label, converter)) {
            replacements.add(new ReplacementImpl(replacementValue, label, converter, required, invocationIDs));
        }
    }

    /* Returns whether the specified converter can be used to replace something at a source that produces the specified label
     * with this instance's replacement value. */
    private boolean canConvert(SourceInfoTaintLabel label, ForcedTypeConverter converter) {
        if(converter == null) {
            return (label.getFullyReplaceable() && ConversionUtils.canBeForceConverted(label.getSourceValueClass(), replacementValue.getClass()))
                    || ConversionUtils.canBeInPlaceForceConverted(label.getSourceValueClass(), replacementValue.getClass());
        } else {
            return converter.canConvert(label.getSourceValueClass(), replacementValue.getClass()) &&
                    (label.getFullyReplaceable() || converter.isInPlace());
        }
    }

    /* Returns a converter that chains the specified converter to an range converter for the specified type and range or
     * null if valid chained converter cannot be made. */
    private ForcedTypeConverter getChainedRangeConverter(ForcedTypeConverter baseConverter, Class targetType, int start, int end) {
        ForcedTypeConverter rangeConverter =  getRangeConverter(targetType, start, end);
        if(baseConverter == null || rangeConverter == null) {
            // If the base converter is null return a converter to just range convert
            // If the range converter is null return null
            return rangeConverter;
        } else if(baseConverter instanceof TargetIndependentConverter) {
            return ChainedTypeConverter.getInstance((TargetIndependentConverter) baseConverter, rangeConverter);
        } else {
            // Base converter can not be chained
            return null;
        }
    }

    /* Returns the range converter for the specified type and range or null if one does not exist for the specified type. */
    private static ForcedTypeConverter getRangeConverter(Class targetType, int start, int end) {
        if(targetType.equals(String.class)) {
            return new SubstringConverter(start, end);
        } else if(targetType.isArray() && targetType.getComponentType().isPrimitive()) {
            return new SubArrayConverter(start, end);
        }  else {
            return null;
        }
    }

    /* Returns whether the specified label meets all of the criteria for a replacement built from this builder. */
    private boolean meetsCriteria(SourceInfoTaintLabel label) {
        if(baseSource != null && !baseSource.equals(label.getBaseSource())) {
            return false;
        } else if(actualSourceClass != null && !actualSourceClass.equals(label.getActualSourceClass())) {
            return false;
        } else return sourceArgIndex == ReplacementImpl.ANY_INT || sourceArgIndex == label.getSourceArgIndex();
    }

    /* Returns whether the specified label could be produced by the the same source class as the one targeted by the
     * replacements generated by this builder. */
    private boolean producibleByTargetedSourceClass(SourceInfoTaintLabel label) {
        if(actualSourceClass != null) {
            return actualSourceClass.equals(label.getActualSourceClass());
        } else if(baseSource != null && baseSource.contains(".") && label.getBaseSource().contains(".")) {
            String labelBaseSourceClass = label.getBaseSource().substring(0, label.getBaseSource().indexOf('.'));
            String baseSourceClass = baseSource.substring(0, baseSource.indexOf('.'));
            return baseSourceClass.equals(labelBaseSourceClass);
        } else {
            return false;
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy