Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
de06a39
feat: integrate new domains in data frame shape inference
OliverGerstl Oct 20, 2025
e346305
feat: creation function for abstract values
OliverGerstl Oct 20, 2025
6503573
feat: positive interval domain extends interval domain
OliverGerstl Oct 20, 2025
af9ba11
feat-fix: type inference for interval top element
OliverGerstl Oct 20, 2025
bdd71a0
feat-fix: typo in satisfiable domain
OliverGerstl Oct 21, 2025
b6af850
Merge branch 'main' into 1965-intregrate-the-new-structure-of-abstrac…
OliverGerstl Oct 21, 2025
d193ff2
feat: better JSON format for df-shape query result
OliverGerstl Oct 21, 2025
21c36eb
Merge branch 'main' into 1965-intregrate-the-new-structure-of-abstrac…
EagleoutIce Oct 24, 2025
c6c9314
feat: include review feedback
OliverGerstl Oct 27, 2025
e338a6f
feat: remove self generic from abstract domains
OliverGerstl Oct 27, 2025
f8c579a
feat-fix: doc comments for abstract domain
OliverGerstl Oct 29, 2025
53c357b
test-fix: string serialization of domains
OliverGerstl Oct 29, 2025
2d0197a
feat: over-approximation for calls with unknown side effects
OliverGerstl Oct 31, 2025
b539fad
feat: make limit for concretize non-optional
OliverGerstl Nov 4, 2025
d519db4
feat: separate joinAll method for domains
OliverGerstl Nov 4, 2025
61df155
feat-fix: restore adapted subtract for positive intervals
OliverGerstl Nov 4, 2025
2e491d2
feat-fix: infer for concrete product type
OliverGerstl Nov 4, 2025
2d94b34
Merge branch 'main' into 1965-intregrate-the-new-structure-of-abstrac…
EagleoutIce Nov 4, 2025
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
26 changes: 15 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).filter(isNotUndefined);
this.newDomain = DataFrameStateDomain.bottom().joinAll(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,14 @@ 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.config.dfg.unknownSideEffects.has(getVertexRootId(vertex))) {
this.newDomain = this.newDomain.bottom();
}
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 +150,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 +159,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
55 changes: 36 additions & 19 deletions src/abstract-interpretation/data-frame/dataframe-domain.ts
Original file line number Diff line number Diff line change
@@ -1,75 +1,92 @@
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()
});
}
}

/**
* 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>());
}
}
Loading