@eagleoutice/flowr-dev
Version:
Static Dataflow Analyzer and Program Slicer for the R Programming Language
347 lines • 14.3 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 value_abstract_domain_1 = require("./value-abstract-domain");
/** The Top element of the set range domain with an empty set as must set and {@link Top} as may set */
exports.SetRangeTop = { must: new Set(), may: lattice_1.Top };
/**
* The set range abstract domain as range of possible value sets with a set of values that must be present and a set of values that may be present
* (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 must set is the empty set and the may set 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 must set and may 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 = abstract_domain_1.DEFAULT_INFERENCE_LIMIT, setType = Set) {
limit = typeof limit === 'number' ? { must: limit, may: limit } : limit;
if (value !== lattice_1.Bottom) {
const mustSet = new setType(value.must);
const maySet = value.may === lattice_1.Top ? lattice_1.Top : new setType(value.may);
const mustExceeds = mustSet.size > limit.must;
const mayExceeds = maySet === lattice_1.Top || maySet.size > limit.may || mustSet.size + maySet.size > limit.must + limit.may;
const must = mustExceeds ? new setType(mustSet.values().take(limit.must)) : mustSet;
const may = mayExceeds ? lattice_1.Top : mustSet.union(maySet).difference(must);
super({ must, may });
}
else {
super(value);
}
this.limit = limit;
this.setType = setType;
}
create(value) {
return new SetRangeDomain(value, this.limit, this.setType);
}
/**
* The set of values that must be present in the set.
*/
get must() {
return (this.isValue() ? this.value.must : lattice_1.Bottom);
}
/**
* The set of values that may be present in the set (can be {@link Top}).
*/
get may() {
return (this.isValue() ? this.value.may : lattice_1.Bottom);
}
lower() {
return this.must;
}
upper() {
return this.isFinite() ? this.must.union(this.may) : this.may;
}
from(...values) {
if (values.length === 0) {
return this.bottom();
}
const sets = values.map(value => new Set(value));
const must = sets.reduce((result, set) => result.intersection(set));
const may = sets.reduce((result, set) => result.union(set)).difference(must);
return this.create({ must, may });
}
static top(limit, setType) {
return new this(exports.SetRangeTop, limit, setType);
}
static bottom(limit, setType) {
return new this(lattice_1.Bottom, limit, setType);
}
static from(values, limit, setType) {
values = !Array.isArray(values) ? [values] : values.every(value => value instanceof Set || Array.isArray(value)) ? values : [values];
if (values.length === 0) {
return this.bottom();
}
const sets = values.map(value => new Set(value));
const must = sets.reduce((result, set) => result.intersection(set));
const may = sets.reduce((result, set) => result.union(set)).difference(must);
return new this({ must, may }, limit, setType);
}
top() {
return this.create(exports.SetRangeTop);
}
bottom() {
return this.create(lattice_1.Bottom);
}
equalsValue(other) {
return (0, set_1.setEquals)(this.must, other.must) && (this.may === other.may || (this.may !== lattice_1.Top && other.may !== lattice_1.Top && (0, set_1.setEquals)(this.may, other.may)));
}
leqValue(other) {
const thisLower = this.lower(), thisUpper = this.upper();
const otherLower = other.lower(), otherUpper = other.upper();
return otherLower.isSubsetOf(thisLower) && (otherUpper === lattice_1.Top || (thisUpper !== lattice_1.Top && thisUpper.isSubsetOf(otherUpper)));
}
joinValue(other) {
const thisLower = this.lower(), thisUpper = this.upper();
const otherLower = other.lower(), otherUpper = other.upper();
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({
must: joinLower,
may: joinUpper === lattice_1.Top ? lattice_1.Top : joinUpper.difference(joinLower)
});
}
meetValue(other) {
const thisLower = this.lower(), thisUpper = this.upper();
const otherLower = other.lower(), otherUpper = other.upper();
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({
must: meetLower,
may: meetUpper === lattice_1.Top ? lattice_1.Top : meetUpper.difference(meetLower)
});
}
union(other) {
other = other instanceof SetRangeDomain ? other : this.create(other);
if (!this.isValue() || !other.isValue()) {
return this.bottom();
}
const thisLower = this.lower(), thisUpper = this.upper();
const otherLower = other.lower(), otherUpper = other.upper();
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({
must: unionLower,
may: unionUpper === lattice_1.Top ? lattice_1.Top : unionUpper.difference(unionLower)
});
}
intersect(other) {
other = other instanceof SetRangeDomain ? other : this.create(other);
if (!this.isValue() || !other.isValue()) {
return this.bottom();
}
const thisLower = this.lower(), thisUpper = this.upper();
const otherLower = other.lower(), otherUpper = other.upper();
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({
must: intersectLower,
may: intersectUpper === lattice_1.Top ? lattice_1.Top : intersectUpper.difference(intersectLower)
});
}
subtract(other) {
other = other instanceof SetRangeDomain ? other : this.create(other);
if (!this.isValue() || !other.isValue()) {
return this.bottom();
}
const thisLower = this.lower(), thisUpper = this.upper();
const otherLower = other.lower(), otherUpper = other.upper();
let subtractLower;
if (otherUpper === lattice_1.Top) {
subtractLower = new Set();
}
else {
subtractLower = thisLower.difference(otherUpper);
}
let subtractUpper;
if (thisUpper === lattice_1.Top) {
subtractUpper = lattice_1.Top;
}
else if (otherUpper === lattice_1.Top) {
subtractUpper = thisUpper.difference(otherLower);
}
else {
subtractUpper = thisUpper.difference(otherUpper);
}
return this.create({
must: subtractLower,
may: subtractUpper === lattice_1.Top ? lattice_1.Top : subtractUpper.difference(subtractLower)
});
}
widenValue(other) {
const thisLower = this.lower(), thisUpper = this.upper();
const otherLower = other.lower(), otherUpper = other.upper();
let widenLower;
if (thisLower.isSubsetOf(otherLower)) {
widenLower = thisLower;
}
else {
widenLower = new Set();
}
let widenUpper;
if (thisUpper !== lattice_1.Top && otherUpper !== lattice_1.Top && otherUpper.isSubsetOf(thisUpper)) {
widenUpper = thisUpper;
}
else {
widenUpper = lattice_1.Top;
}
return this.create({
must: widenLower,
may: widenUpper === lattice_1.Top ? lattice_1.Top : widenUpper.difference(widenLower)
});
}
narrowValue(other) {
const thisLower = this.lower(), thisUpper = this.upper();
const otherLower = other.lower(), otherUpper = other.upper();
if (this.meetValue(other).isBottom()) {
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({
must: narrowLower,
may: narrowUpper === lattice_1.Top ? lattice_1.Top : narrowUpper.difference(narrowLower)
});
}
satisfies(set, comparator = value_abstract_domain_1.SetComparator.Equal) {
const lower = this.lower(), upper = this.upper();
if (lower === lattice_1.Bottom || upper === lattice_1.Bottom) {
return logic_1.Ternary.Never;
}
const value = new this.setType(set);
switch (comparator) {
case value_abstract_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 value_abstract_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 value_abstract_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;
}
case value_abstract_domain_1.SetComparator.Intersect: {
if (this.isInfinite() || (this.isFinite() && [...set].some(value => this.may.has(value)))) {
return (this.isValue() && [...set].some(value => this.must.has(value))) ? logic_1.Ternary.Always : logic_1.Ternary.Maybe;
}
return logic_1.Ternary.Never;
}
case value_abstract_domain_1.SetComparator.NoIntersect: {
return logic_1.TernaryLogic.negate(this.satisfies(set, value_abstract_domain_1.SetComparator.Intersect));
}
default: {
(0, assert_1.assertUnreachable)(comparator);
}
}
}
/**
* Extends the must set of the current abstract value down to the empty set.
*/
widenDown() {
if (this.isValue()) {
return this.create({ must: new this.setType(), may: this.upper() });
}
return this.bottom();
}
/**
* Extends the may set of the current abstract value up to {@link Top}.
*/
widenUp() {
if (this.isValue()) {
return this.create({ must: this.must, may: lattice_1.Top });
}
return this.bottom();
}
jsonify() {
const must = this.must.values().toArray();
const may = this.may === lattice_1.Top ? lattice_1.Top.description : this.may.values().toArray();
return { must, may };
}
stringify() {
const must = this.must.values().map(abstract_domain_1.AbstractDomain.toString).toArray().join(', ');
if (this.may === lattice_1.Top) {
return `[{${must}}, ${lattice_1.TopSymbol}]`;
}
const may = this.may.values().map(abstract_domain_1.AbstractDomain.toString).toArray().join(', ');
return `[{${must}}, {${may}}]`;
}
isTop() {
return this.isValue() && this.must.size === 0 && this.may === lattice_1.Top;
}
isBottom() {
return this.value === lattice_1.Bottom;
}
isValue() {
return this.value !== lattice_1.Bottom;
}
isFinite() {
return this.isValue() && this.may !== lattice_1.Top;
}
isInfinite() {
return this.isValue() && this.may === lattice_1.Top;
}
}
exports.SetRangeDomain = SetRangeDomain;
//# sourceMappingURL=set-range-domain.js.map