UNPKG

@eagleoutice/flowr

Version:

Static Dataflow Analyzer and Program Slicer for the R Programming Language

406 lines 17.8 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.SetRangeDomain = exports.SetRangeTop = void 0; const assert_1 = require("../../util/assert"); const set_1 = require("../../util/collections/set"); const logic_1 = require("../../util/logic"); const abstract_domain_1 = require("./abstract-domain"); const lattice_1 = require("./lattice"); const satisfiable_domain_1 = require("./satisfiable-domain"); /* eslint-disable @typescript-eslint/unified-signatures */ /** The Top element of the set range domain with an empty set as minimum set and {@link Top} as range set */ exports.SetRangeTop = { min: new Set(), range: lattice_1.Top }; const DefaultLimit = { min: abstract_domain_1.DEFAULT_INFERENCE_LIMIT, range: abstract_domain_1.DEFAULT_INFERENCE_LIMIT }; /** * The set range abstract domain as range of possible value sets with a minimum set of values and a range of possible additional values * (similar to an interval-like structure with a lower bound and a difference to the upper bound). * The Bottom element is defined as {@link Bottom} symbol and the Top element is defined as the range `[∅, Top]` where the minimum set is the empty set and the range is {@link Top}. * @template T - Type of the values in the sets in the abstract domain * @template Value - Type of the constraint in the abstract domain (Top, Bottom, or an actual value) */ class SetRangeDomain extends abstract_domain_1.AbstractDomain { limit; setType; /** * @param limit - A limit for the maximum number of elements to store in the minimum set and maximum set before over-approximation * @param newSet - An optional set constructor for the domain elements if the type `T` is not storable in a HashSet */ constructor(value, limit = DefaultLimit, setType = Set) { limit = typeof limit === 'number' ? { min: limit, range: limit } : limit; if (value !== lattice_1.Bottom) { const minSet = new setType(value.min); const rangeSet = value.range === lattice_1.Top ? lattice_1.Top : new setType(value.range); const minExceeds = minSet.size > limit.min; const rangeExceeds = rangeSet === lattice_1.Top || rangeSet.size > limit.range || minSet.size + rangeSet.size > limit.min + limit.range; const min = minExceeds ? new setType(minSet.values().take(limit.min)) : minSet; const range = rangeExceeds ? lattice_1.Top : minSet.union(rangeSet).difference(min); super({ min, range }); } else { super(value); } this.limit = limit; this.setType = setType; } create(value) { return new SetRangeDomain(value, this.limit, this.setType); } /** * The minimum set (lower bound) of the set range representing all values that must exist (subset of {@link upper}). */ lower() { if (this.value === lattice_1.Bottom) { return lattice_1.Bottom; } return this.value.min; } /** * The maximum set (upper bound) of the set range representing all values that can possibly exist (union of {@link lower} and range). */ upper() { if (this.value === lattice_1.Bottom) { return lattice_1.Bottom; } else if (this.value.range === lattice_1.Top) { return lattice_1.Top; } return this.value.min.union(this.value.range); } static top(limit, setType) { return new SetRangeDomain(exports.SetRangeTop, limit, setType); } static bottom(limit, setType) { return new SetRangeDomain(lattice_1.Bottom, limit, setType); } static abstract(concrete, limit, setType) { if (concrete === lattice_1.Top) { return SetRangeDomain.top(limit, setType); } else if (concrete.size === 0) { return SetRangeDomain.bottom(limit, setType); } const lower = concrete.values().reduce((result, set) => result.intersection(set)); const upper = concrete.values().reduce((result, set) => result.union(set)); return new SetRangeDomain({ min: lower, range: upper.difference(lower) }, limit, setType); } top() { return SetRangeDomain.top(this.limit, this.setType); } bottom() { return SetRangeDomain.bottom(this.limit, this.setType); } equals(other) { if (this.value === other.value) { return true; } else if (this.value === lattice_1.Bottom || other.value === lattice_1.Bottom || !(0, set_1.setEquals)(this.value.min, other.value.min)) { return false; } else if (this.value.range === other.value.range) { return true; } return this.value.range !== lattice_1.Top && other.value.range !== lattice_1.Top && (0, set_1.setEquals)(this.value.range, other.value.range); } leq(other) { const thisLower = this.lower(), thisUpper = this.upper(); const otherLower = other.lower(), otherUpper = other.upper(); if (thisLower === lattice_1.Bottom || thisUpper === lattice_1.Bottom) { return true; } else if (otherLower === lattice_1.Bottom || otherUpper === lattice_1.Bottom || !otherLower.isSubsetOf(thisLower)) { return false; } else if (otherUpper === lattice_1.Top) { return true; } return thisUpper !== lattice_1.Top && thisUpper.isSubsetOf(otherUpper); } join(other) { other = other instanceof SetRangeDomain ? other : this.create(other); const thisLower = this.lower(), thisUpper = this.upper(); const otherLower = other.lower(), otherUpper = other.upper(); if (thisLower === lattice_1.Bottom || thisUpper === lattice_1.Bottom) { return this.create(other.value); } else if (otherLower === lattice_1.Bottom || otherUpper === lattice_1.Bottom) { return this.create(this.value); } const joinLower = thisLower.intersection(otherLower); let joinUpper; if (thisUpper === lattice_1.Top || otherUpper === lattice_1.Top) { joinUpper = lattice_1.Top; } else { joinUpper = thisUpper.union(otherUpper); } return this.create({ min: joinLower, range: joinUpper === lattice_1.Top ? lattice_1.Top : joinUpper.difference(joinLower) }); } meet(other) { other = other instanceof SetRangeDomain ? other : this.create(other); const thisLower = this.lower(), thisUpper = this.upper(); const otherLower = other.lower(), otherUpper = other.upper(); if (thisLower === lattice_1.Bottom || thisUpper === lattice_1.Bottom || otherLower === lattice_1.Bottom || otherUpper === lattice_1.Bottom) { return this.bottom(); } const meetLower = thisLower.union(otherLower); let meetUpper; if (thisUpper === lattice_1.Top) { meetUpper = otherUpper; } else if (otherUpper === lattice_1.Top) { meetUpper = thisUpper; } else { meetUpper = thisUpper.intersection(otherUpper); } if (meetUpper !== lattice_1.Top && !meetLower.isSubsetOf(meetUpper)) { return this.bottom(); } return this.create({ min: meetLower, range: meetUpper === lattice_1.Top ? lattice_1.Top : meetUpper.difference(meetLower) }); } /** * Creates the union of this abstract value and another abstract value by creating the union of the minimum and maximum set, respectively. */ union(other) { other = other instanceof SetRangeDomain ? other : this.create(other); const thisLower = this.lower(), thisUpper = this.upper(); const otherLower = other.lower(), otherUpper = other.upper(); if (thisLower === lattice_1.Bottom || thisUpper === lattice_1.Bottom) { return this.create(other.value); } else if (otherLower === lattice_1.Bottom || otherUpper === lattice_1.Bottom) { return this.create(this.value); } const unionLower = thisLower.union(otherLower); let unionUpper; if (thisUpper === lattice_1.Top || otherUpper === lattice_1.Top) { unionUpper = lattice_1.Top; } else { unionUpper = thisUpper.union(otherUpper); } return this.create({ min: unionLower, range: unionUpper === lattice_1.Top ? lattice_1.Top : unionUpper.difference(unionLower) }); } /** * Creates the intersection of this abstract value and another abstract value by creating the intersection of the minimum and maximum set, respectively. */ intersect(other) { other = other instanceof SetRangeDomain ? other : this.create(other); const thisLower = this.lower(), thisUpper = this.upper(); const otherLower = other.lower(), otherUpper = other.upper(); if (thisLower === lattice_1.Bottom || thisUpper === lattice_1.Bottom || otherLower === lattice_1.Bottom || otherUpper === lattice_1.Bottom) { return this.bottom(); } const intersectLower = thisLower.intersection(otherLower); let intersectUpper; if (thisUpper === lattice_1.Top) { intersectUpper = otherUpper; } else if (otherUpper === lattice_1.Top) { intersectUpper = thisUpper; } else { intersectUpper = thisUpper.intersection(otherUpper); } return this.create({ min: intersectLower, range: intersectUpper === lattice_1.Top ? lattice_1.Top : intersectUpper.difference(intersectLower) }); } /** * Subtracts another abstract value from the current abstract value by removing all elements of the other abstract value from the current abstract value. */ subtract(other) { other = other instanceof SetRangeDomain ? other : this.create(other); const thisLower = this.lower(), thisUpper = this.upper(); const otherLower = other.lower(), otherUpper = other.upper(); if (thisLower === lattice_1.Bottom || thisUpper === lattice_1.Bottom) { return this.bottom(); } else if (otherLower === lattice_1.Bottom || otherUpper === lattice_1.Bottom) { return this.create(this.value); } let subLower; if (otherUpper === lattice_1.Top) { subLower = new Set(); } else { subLower = thisLower.difference(otherUpper); } let subUpper; if (thisUpper === lattice_1.Top) { subUpper = lattice_1.Top; } else if (otherUpper === lattice_1.Top) { subUpper = thisUpper.difference(otherLower); } else { subUpper = thisUpper.difference(otherUpper); } return this.create({ min: subLower, range: subUpper === lattice_1.Top ? lattice_1.Top : subUpper.difference(subLower) }); } widen(other) { const thisLower = this.lower(), thisUpper = this.upper(); const otherLower = other.lower(), otherUpper = other.upper(); if (thisLower === lattice_1.Bottom || thisUpper === lattice_1.Bottom) { return this.create(other.value); } else if (otherLower === lattice_1.Bottom || otherUpper === lattice_1.Bottom) { return this.create(this.value); } let widenLower; if (!thisLower.isSubsetOf(otherLower)) { widenLower = new Set(); } else { widenLower = thisLower; } let widenUpper; if (thisUpper === lattice_1.Top || otherUpper === lattice_1.Top || !otherUpper.isSubsetOf(thisUpper)) { widenUpper = lattice_1.Top; } else { widenUpper = thisUpper; } return this.create({ min: widenLower, range: widenUpper === lattice_1.Top ? lattice_1.Top : widenUpper.difference(widenLower) }); } narrow(other) { const thisLower = this.lower(), thisUpper = this.upper(); const otherLower = other.lower(), otherUpper = other.upper(); if (thisLower === lattice_1.Bottom || thisUpper === lattice_1.Bottom || otherLower === lattice_1.Bottom || otherUpper === lattice_1.Bottom) { return this.bottom(); } let meetUpper; if (thisUpper === lattice_1.Top) { meetUpper = otherUpper; } else if (otherUpper === lattice_1.Top) { meetUpper = thisUpper; } else { meetUpper = thisUpper.intersection(otherUpper); } if (meetUpper !== lattice_1.Top && !thisLower.union(otherLower).isSubsetOf(meetUpper)) { return this.bottom(); } let narrowLower; if (thisLower.size === 0) { narrowLower = otherLower; } else { narrowLower = thisLower; } let narrowUpper; if (thisUpper === lattice_1.Top) { narrowUpper = otherUpper; } else { narrowUpper = thisUpper; } return this.create({ min: narrowLower, range: narrowUpper === lattice_1.Top ? lattice_1.Top : narrowUpper.difference(narrowLower) }); } concretize(limit) { if (this.value === lattice_1.Bottom) { return new Set(); } else if (this.value.range === lattice_1.Top || 2 ** (this.value.range.size) > limit) { return lattice_1.Top; } const subsets = [new this.setType()]; for (const element of this.value.range) { const newSubsets = subsets.map(subset => new this.setType([...subset, element])); for (const subset of newSubsets) { subsets.push(subset); } } return new Set(subsets.map(subset => this.value === lattice_1.Bottom ? subset : this.value.min.union(subset))); } abstract(concrete) { return SetRangeDomain.abstract(concrete, this.limit); } satisfies(set, comparator = satisfiable_domain_1.SetComparator.Equal) { const value = new this.setType(set); const lower = this.lower(), upper = this.upper(); if (lower === lattice_1.Bottom || upper === lattice_1.Bottom) { return logic_1.Ternary.Never; } switch (comparator) { case satisfiable_domain_1.SetComparator.Equal: { if (lower.isSubsetOf(value) && (upper === lattice_1.Top || value.isSubsetOf(upper))) { return upper !== lattice_1.Top && lower.size === upper.size ? logic_1.Ternary.Always : logic_1.Ternary.Maybe; } return logic_1.Ternary.Never; } case satisfiable_domain_1.SetComparator.SubsetOrEqual: { if (upper === lattice_1.Top || value.isSubsetOf(upper)) { return value.isSubsetOf(lower) ? logic_1.Ternary.Always : logic_1.Ternary.Maybe; } return logic_1.Ternary.Never; } case satisfiable_domain_1.SetComparator.Subset: { if (upper === lattice_1.Top || (value.isSubsetOf(upper) && !(0, set_1.setEquals)(value, upper))) { return value.isSubsetOf(lower) && !(0, set_1.setEquals)(value, lower) ? logic_1.Ternary.Always : logic_1.Ternary.Maybe; } return logic_1.Ternary.Never; } default: { (0, assert_1.assertUnreachable)(comparator); } } } /** * Extends the minimum set of the current abstract value down to the empty set. */ widenDown() { const upper = this.upper(); if (upper === lattice_1.Bottom) { return this.bottom(); } else { return this.create({ min: new this.setType(), range: upper }); } } /** * Extends the maximum set of the current abstract value up to {@link Top}. */ widenUp() { const lower = this.lower(); if (lower === lattice_1.Bottom) { return this.bottom(); } else { return this.create({ min: lower, range: lattice_1.Top }); } } toJson() { if (this.value === lattice_1.Bottom) { return this.value.description; } const min = this.value.min.values().toArray(); const range = this.value.range === lattice_1.Top ? this.value.range.description : this.value.range.values().toArray(); return { min, range }; } toString() { if (this.value === lattice_1.Bottom) { return lattice_1.BottomSymbol; } else if (this.value.range === lattice_1.Top) { const minString = this.value.min.values().map(abstract_domain_1.domainElementToString).toArray().join(', '); return `[{${minString}}, ${lattice_1.TopSymbol}]`; } const minString = this.value.min.values().map(abstract_domain_1.domainElementToString).toArray().join(', '); const rangeString = this.value.range.values().map(abstract_domain_1.domainElementToString).toArray().join(', '); return `[{${minString}}, {${rangeString}}]`; } isTop() { return this.value !== lattice_1.Bottom && this.value.min.size === 0 && this.value.range === lattice_1.Top; } isBottom() { return this.value === lattice_1.Bottom; } isValue() { return this.value !== lattice_1.Bottom; } isFinite() { return this.value !== lattice_1.Bottom && this.value.range !== lattice_1.Top; } } exports.SetRangeDomain = SetRangeDomain; //# sourceMappingURL=set-range-domain.js.map