Skip to content
Open
Show file tree
Hide file tree
Changes from 10 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/abstract-interpretation/data-frame/absint-info.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { RNode } from '../../r-bridge/lang-4.x/ast/model/model';
import type { ParentInformation } from '../../r-bridge/lang-4.x/ast/model/processing/decorate';
import type { NodeId } from '../../r-bridge/lang-4.x/ast/model/processing/node-id';
import type { DataFrameStateDomain } from './domain';
import type { DataFrameStateDomain } from './dataframe-domain';
import type { ConstraintType, DataFrameOperationArgs, DataFrameOperationName, DataFrameOperationOptions } from './semantics';

/**
Expand Down
23 changes: 12 additions & 11 deletions src/abstract-interpretation/data-frame/absint-visitor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,7 @@ import type { NormalizedAst, ParentInformation } from '../../r-bridge/lang-4.x/a
import type { NodeId } from '../../r-bridge/lang-4.x/ast/model/processing/node-id';
import { isNotUndefined } from '../../util/assert';
import { DataFrameInfoMarker, hasDataFrameAssignmentInfo, hasDataFrameExpressionInfo, hasDataFrameInfoMarker, type AbstractInterpretationInfo } from './absint-info';
import type { DataFrameDomain, DataFrameStateDomain } from './domain';
import { DataFrameTop, equalDataFrameState, joinDataFrameStates, wideningDataFrameStates } from './domain';
import { DataFrameDomain, DataFrameStateDomain } from './dataframe-domain';
import { mapDataFrameAccess } from './mappers/access-mapper';
import { isAssignmentTarget, mapDataFrameVariableAssignment } from './mappers/assignment-mapper';
import { mapDataFrameFunctionCall } from './mappers/function-mapper';
Expand Down Expand Up @@ -39,12 +38,12 @@ export class DataFrameShapeInferenceVisitor<
* The old domain of an AST node before processing the node retrieved from the attached {@link AbstractInterpretationInfo}.
* This is used to check whether the state has changed and successors should be visited again, and is also required for widening.
*/
private oldDomain: DataFrameStateDomain = new Map();
private oldDomain = DataFrameStateDomain.bottom();
/**
* The new domain of an AST node during and after processing the node.
* This information is stored in the {@link AbstractInterpretationInfo} afterward.
*/
private newDomain: DataFrameStateDomain = new Map();
private newDomain = DataFrameStateDomain.bottom();

constructor(config: Config) {
super({ ...config, defaultVisitingOrder: 'forward', defaultVisitingType: 'exit' });
Expand All @@ -58,14 +57,15 @@ export class DataFrameShapeInferenceVisitor<
return true;
}
const predecessors = this.getPredecessorNodes(vertex.id);
this.newDomain = joinDataFrameStates(...predecessors.map(node => node.info.dataFrame?.domain ?? new Map<NodeId, DataFrameDomain>()));
const predecessorDomains = predecessors.map(node => node.info.dataFrame?.domain ?? DataFrameStateDomain.bottom());
this.newDomain = DataFrameStateDomain.join(predecessorDomains);
this.onVisitNode(nodeId);

const visitedCount = this.visited.get(vertex.id) ?? 0;
this.visited.set(vertex.id, visitedCount + 1);

// only continue visiting if the node has not been visited before or the data frame value of the node changed
return visitedCount === 0 || !equalDataFrameState(this.oldDomain, this.newDomain);
return visitedCount === 0 || !this.oldDomain.equals(this.newDomain);
}

protected override visitDataflowNode(vertex: Exclude<CfgSimpleVertex, CfgBasicBlockVertex>): void {
Expand All @@ -74,11 +74,11 @@ export class DataFrameShapeInferenceVisitor<
if(node === undefined) {
return;
}
this.oldDomain = node.info.dataFrame?.domain ?? new Map<NodeId, DataFrameDomain>();
this.oldDomain = node.info.dataFrame?.domain ?? DataFrameStateDomain.bottom();
super.visitDataflowNode(vertex);

if(this.shouldWiden(vertex)) {
this.newDomain = wideningDataFrameStates(this.oldDomain, this.newDomain);
this.newDomain = this.oldDomain.widen(this.newDomain);
}
node.info.dataFrame ??= {};
node.info.dataFrame.domain = this.newDomain;
Expand Down Expand Up @@ -147,7 +147,7 @@ export class DataFrameShapeInferenceVisitor<

if(identifier !== undefined) {
identifier.info.dataFrame ??= {};
identifier.info.dataFrame.domain = new Map(this.newDomain);
identifier.info.dataFrame.domain = this.newDomain.create(this.newDomain.value);
}
}
}
Expand All @@ -156,11 +156,12 @@ export class DataFrameShapeInferenceVisitor<
if(!hasDataFrameExpressionInfo(node)) {
return;
}
let value: DataFrameDomain = DataFrameTop;
const maxColNames = this.config.flowrConfig.abstractInterpretation.dataFrame.maxColNames;
let value = DataFrameDomain.top(maxColNames);

for(const { operation, operand, type, options, ...args } of node.info.dataFrame.operations) {
const operandValue = operand !== undefined ? resolveIdToDataFrameShape(operand, this.config.dfg, this.newDomain) : value;
value = applyDataFrameSemantics(operation, operandValue ?? DataFrameTop, args, options);
value = applyDataFrameSemantics(operation, operandValue ?? DataFrameDomain.top(maxColNames), args, options);
const constraintType = type ?? getConstraintType(operation);

if(operand !== undefined && constraintType === ConstraintType.OperandModification) {
Expand Down
71 changes: 52 additions & 19 deletions src/abstract-interpretation/data-frame/dataframe-domain.ts
Original file line number Diff line number Diff line change
@@ -1,75 +1,108 @@
import type { AbstractDomain } from '../domains/abstract-domain';
import type { NodeId } from '../../r-bridge/lang-4.x/ast/model/processing/node-id';
import type { AbstractDomainValue } from '../domains/abstract-domain';
import { PosIntervalDomain } from '../domains/positive-interval-domain';
import { ProductDomain } from '../domains/product-domain';
import { SetBoundedSetDomain } from '../domains/set-bounded-set-domain';
import { SetUpperBoundDomain } from '../domains/set-upper-bound-domain';
import { StateAbstractDomain } from '../domains/state-abstract-domain';

/** The type of the abstract product representing the shape of data frames */
export type AbstractDataFrameShape = {
colnames: SetBoundedSetDomain<string>;
colnames: SetUpperBoundDomain<string>;
cols: PosIntervalDomain;
rows: PosIntervalDomain;
}

/** The type of abstract values of a sub abstract domain (shape property) of the data frame shape product */
type DataFrameShapeProperty<Property extends keyof AbstractDataFrameShape> =
AbstractDataFrameShape[Property] extends AbstractDomain<unknown, unknown, unknown, unknown, infer Lift> ? Lift : never;
/** The type of abstract values of a sub abstract domain (shape property) of the data frame shape product domain */
export type DataFrameShapeProperty<Property extends keyof AbstractDataFrameShape> = AbstractDomainValue<AbstractDataFrameShape[Property]>;

/**
* The data frame abstract domain as product domain of a column names domain, column count domain, and row count domain.
*/
export class DataFrameDomain extends ProductDomain<AbstractDataFrameShape> {
constructor(value: AbstractDataFrameShape, maxColNames?: number) {
super({
colnames: new SetBoundedSetDomain(value.colnames.value, maxColNames),
colnames: new SetUpperBoundDomain(value.colnames.value, maxColNames ?? value.colnames.limit),
cols: new PosIntervalDomain(value.cols.value),
rows: new PosIntervalDomain(value.rows.value)
});
}

public create(value: AbstractDataFrameShape, maxColNames?: number): DataFrameDomain {
return new DataFrameDomain(value, maxColNames);
public create(value: AbstractDataFrameShape): this;
public create(value: AbstractDataFrameShape): DataFrameDomain {
return new DataFrameDomain(value, this.maxColNames);
}

/**
* The current abstract value of the column names domain.
*/
public get colnames(): DataFrameShapeProperty<'colnames'> {
return this.value.colnames.value;
public get colnames(): AbstractDataFrameShape['colnames'] {
return this.value.colnames;
}

/**
* The current abstract value of the column count domain.
*/
public get cols(): DataFrameShapeProperty<'cols'> {
return this.value.cols.value;
public get cols(): AbstractDataFrameShape['cols'] {
return this.value.cols;
}

/**
* The current abstract value of the row count domain.
*/
public get rows(): DataFrameShapeProperty<'rows'> {
return this.value.rows.value;
public get rows(): AbstractDataFrameShape['rows'] {
return this.value.rows;
}

/**
* The maximum number of inferred column names of the column names domain.
*/
public get maxColNames(): number {
return this.value.colnames.limit;
}

public static bottom(maxColNames?: number): DataFrameDomain {
return new DataFrameDomain({
colnames: SetBoundedSetDomain.bottom(maxColNames),
colnames: SetUpperBoundDomain.bottom(maxColNames),
cols: PosIntervalDomain.bottom(),
rows: PosIntervalDomain.bottom()
});
}

public static top(maxColNames?: number): DataFrameDomain {
return new DataFrameDomain({
colnames: SetBoundedSetDomain.top(maxColNames),
colnames: SetUpperBoundDomain.top(maxColNames),
cols: PosIntervalDomain.top(),
rows: PosIntervalDomain.top()
});
}

public static join(values: DataFrameDomain[]): DataFrameDomain {
return values[0]?.join(...values.slice(1)) ?? DataFrameDomain.bottom();
}

public static meet(values: DataFrameDomain[]): DataFrameDomain {
return values[0]?.meet(...values.slice(1)) ?? DataFrameDomain.bottom();
}
}

/**
* The data frame state abstract domain as state abstract domain mapping AST node IDs to inferred abstract data frame shapes.
* The data frame state abstract domain as state domain mapping AST node IDs to inferred abstract data frame shapes.
*/
export class DateFrameStateDomain extends StateAbstractDomain<DataFrameDomain> {}
export class DataFrameStateDomain extends StateAbstractDomain<DataFrameDomain> {
public create(value: ReadonlyMap<NodeId, DataFrameDomain>): this;
public create(value: ReadonlyMap<NodeId, DataFrameDomain>): DataFrameStateDomain {
return new DataFrameStateDomain(value);
}

public static bottom(): DataFrameStateDomain {
return new DataFrameStateDomain(new Map<NodeId, DataFrameDomain>());
}

public static join(values: DataFrameStateDomain[]): DataFrameStateDomain {
return values[0]?.join(...values.slice(1)) ?? DataFrameStateDomain.bottom();
}

public static meet(values: DataFrameStateDomain[]): DataFrameStateDomain {
return values[0]?.meet(...values.slice(1)) ?? DataFrameStateDomain.bottom();
}
}
Loading