@eagleoutice/flowr
Version:
Static Dataflow Analyzer and Program Slicer for the R Programming Language
406 lines • 17.8 kB
JavaScript
"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