Skip to content

Commit

Permalink
feat-fix(pointer-analysis): expand configuration to enforce disable
Browse files Browse the repository at this point in the history
  • Loading branch information
EagleoutIce committed Jan 6, 2025
1 parent 6249f30 commit 0ca7566
Show file tree
Hide file tree
Showing 6 changed files with 97 additions and 54 deletions.
3 changes: 2 additions & 1 deletion src/dataflow/environments/resolve-by-name.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@ const TargetTypePredicate = {
* @param environment - The current environment used for name resolution
* @param target - The target (meta) type of the identifier to resolve
*
* @returns A list of possible definitions of the identifier (one if the definition location is exactly and always known), or `undefined` if the identifier is undefined in the current scope/with the current environment information.
* @returns A list of possible identifier definitions (one if the definition location is exactly and always known), or `undefined`
* if the identifier is undefined in the current scope/with the current environment information.
*/
export function resolveByName(name: Identifier, environment: REnvironmentInformation, target: ReferenceType = ReferenceType.Unknown): IdentifierDefinition[] | undefined {
let current: IEnvironment = environment.current;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import { isParentContainerIndex } from '../../../../../graph/vertex';
import type { RArgument } from '../../../../../../r-bridge/lang-4.x/ast/model/nodes/r-argument';
import { RoleInParent } from '../../../../../../r-bridge/lang-4.x/ast/model/processing/role';
import { filterIndices, resolveSingleIndex } from '../../../../../../util/list-access';
import { getConfig } from '../../../../../../config';

interface TableAssignmentProcessorMarker {
definitionRootNodes: NodeId[]
Expand Down Expand Up @@ -200,35 +201,37 @@ function processStringBasedAccess<OtherInfo>(
return fnCall;
}

let accessedIndicesCollection: ContainerIndicesCollection;
// If the accessedArg is a symbol, it's either a simple access or the base case of a nested access
if(accessedArg.value?.type === RType.Symbol) {
accessedIndicesCollection = resolveSingleIndex(accessedArg, accessArg, data.environment);
} else {
// Higher access call
const underlyingAccessId = accessedArg.value?.info.id ?? -1;
const vertex = fnCall.information.graph.getVertex(underlyingAccessId);
const subIndices = vertex?.indicesCollection
?.flatMap(indices => indices.indices)
?.flatMap(index => (index as ContainerParentIndex)?.subIndices ?? []);
if(subIndices) {
accessedIndicesCollection = filterIndices(subIndices, accessArg);
if(getConfig().solver.pointerTracking) {
let accessedIndicesCollection: ContainerIndicesCollection;
// If the accessedArg is a symbol, it's either a simple access or the base case of a nested access
if(accessedArg.value?.type === RType.Symbol) {
accessedIndicesCollection = resolveSingleIndex(accessedArg, accessArg, data.environment);
} else {
// Higher access call
const underlyingAccessId = accessedArg.value?.info.id ?? -1;
const vertex = fnCall.information.graph.getVertex(underlyingAccessId);
const subIndices = vertex?.indicesCollection
?.flatMap(indices => indices.indices)
?.flatMap(index => (index as ContainerParentIndex)?.subIndices ?? []);
if(subIndices) {
accessedIndicesCollection = filterIndices(subIndices, accessArg);
}
}
}

// Add indices to vertex afterward
if(accessedIndicesCollection) {
const vertex = fnCall.information.graph.getVertex(rootId);
if(vertex) {
vertex.indicesCollection = accessedIndicesCollection;
}
// Add indices to vertex afterward
if(accessedIndicesCollection) {
const vertex = fnCall.information.graph.getVertex(rootId);
if(vertex) {
vertex.indicesCollection = accessedIndicesCollection;
}

// When access has no access as parent, it's the top most
const rootNode = data.completeAst.idMap.get(rootId);
const parentNode = data.completeAst.idMap.get(rootNode?.info.parent ?? -1);
if(parentNode?.type !== RType.Access) {
// Only reference indices in top most access
referenceIndices(accessedIndicesCollection, fnCall, name.info.id);
// When access has no access as parent, it's the top most
const rootNode = data.completeAst.idMap.get(rootId);
const parentNode = data.completeAst.idMap.get(rootNode?.info.parent ?? -1);
if(parentNode?.type !== RType.Access) {
// Only reference indices in top most access
referenceIndices(accessedIndicesCollection, fnCall, name.info.id);
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import type { REnvironmentInformation } from '../../../../../environments/enviro
import type { DataflowGraph } from '../../../../../graph/graph';
import { getAliases } from '../../../../../environments/resolve-by-name';
import { addSubIndicesToLeafIndices } from '../../../../../../util/list-access';
import { getConfig } from '../../../../../../config';

function toReplacementSymbol<OtherInfo>(target: RNodeWithParent<OtherInfo & ParentInformation> & Base<OtherInfo> & Location, prefix: string, superAssignment: boolean): RSymbol<OtherInfo & ParentInformation> {
return {
Expand Down Expand Up @@ -251,8 +252,7 @@ export interface AssignmentToSymbolParameters<OtherInfo> extends AssignmentConfi
* @param nodeToDefine - `x`
* @param sourceIds - `v`
* @param rootIdOfAssignment - `<-`
* @param quoteSource - whether to quote the source (i.e., define `x` without a direct reference to `v`)
* @param superAssignment - whether this is a super assignment (i.e., `<<-`)
* @param config - configuration for the assignment processing
*/
export function markAsAssignment(
information: {
Expand All @@ -264,24 +264,26 @@ export function markAsAssignment(
rootIdOfAssignment: NodeId,
config?: AssignmentConfiguration | undefined,
) {
let indicesCollection: ContainerIndicesCollection = undefined;
if(sourceIds.length === 1) {
// support for tracking indices
// Indices were defined for the vertex e.g. a <- list(c = 1) or a$b <- list(c = 1)
indicesCollection = information.graph.getVertex(sourceIds[0])?.indicesCollection;
}
// Indices defined by replacement operation e.g. $<-
if(config?.indicesCollection !== undefined) {
// If there were indices stored in the vertex, then a container was defined
// and assigned to the index of another container e.g. a$b <- list(c = 1)
if(indicesCollection) {
indicesCollection = addSubIndicesToLeafIndices(config.indicesCollection, indicesCollection);
} else {
// No indices were defined for the vertex e.g. a$b <- 2
indicesCollection = config.indicesCollection;
if(getConfig().solver.pointerTracking) {
let indicesCollection: ContainerIndicesCollection = undefined;
if(sourceIds.length === 1) {
// support for tracking indices
// Indices were defined for the vertex e.g. a <- list(c = 1) or a$b <- list(c = 1)
indicesCollection = information.graph.getVertex(sourceIds[0])?.indicesCollection;
}
// Indices defined by replacement operation e.g. $<-
if(config?.indicesCollection !== undefined) {
// If there were indices stored in the vertex, then a container was defined
// and assigned to the index of another container e.g. a$b <- list(c = 1)
if(indicesCollection) {
indicesCollection = addSubIndicesToLeafIndices(config.indicesCollection, indicesCollection);
} else {
// No indices were defined for the vertex e.g. a$b <- 2
indicesCollection = config.indicesCollection;
}
}
nodeToDefine.indicesCollection ??= indicesCollection;
}
nodeToDefine.indicesCollection ??= indicesCollection;

information.environment = define(nodeToDefine, config?.superAssignment, information.environment);
information.graph.setDefinitionOfVertex(nodeToDefine);
Expand All @@ -291,12 +293,14 @@ export function markAsAssignment(
}
}
information.graph.addEdge(nodeToDefine, rootIdOfAssignment, EdgeType.DefinedBy);
// kinda dirty, but we have to remove existing read edges for the symbol, added by the child
const out = information.graph.outgoingEdges(nodeToDefine.nodeId);
for(const [id,edge] of (out?? [])) {
edge.types &= ~EdgeType.Reads;
if(edge.types == 0) {
out?.delete(id);
if(getConfig().solver.pointerTracking) {
// kinda dirty, but we have to remove existing read edges for the symbol, added by the child
const out = information.graph.outgoingEdges(nodeToDefine.nodeId);
for(const [id, edge] of (out ?? [])) {
edge.types &= ~EdgeType.Reads;
if(edge.types == 0) {
out?.delete(id);
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import { graphToMermaidUrl } from '../../../../../../util/mermaid/dfg';
import { RoleInParent } from '../../../../../../r-bridge/lang-4.x/ast/model/processing/role';
import { RType } from '../../../../../../r-bridge/lang-4.x/ast/model/type';
import { constructNestedAccess } from '../../../../../../util/list-access';
import { getConfig } from '../../../../../../config';

export function processReplacementFunction<OtherInfo>(
name: RSymbol<OtherInfo & ParentInformation>,
Expand All @@ -38,7 +39,7 @@ export function processReplacementFunction<OtherInfo>(
expensiveTrace(dataflowLogger, () => `Replacement ${name.content} with ${JSON.stringify(args)}, processing`);

let indices: ContainerIndicesCollection = undefined;
if(name.content === '$<-') {
if(name.content === '$<-' && getConfig().solver.pointerTracking) {
const nonEmptyArgs = args.filter(arg => arg !== EmptyArgument);
const accessedArg = nonEmptyArgs.find(arg => arg.info.role === RoleInParent.Accessed);
const accessArg = nonEmptyArgs.find(arg => arg.info.role === RoleInParent.IndexAccess);
Expand Down
19 changes: 19 additions & 0 deletions test/functionality/_helper/config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import type { FlowrConfigOptions } from '../../../src/config';
import { setConfig , getConfig } from '../../../src/config';
import { afterAll, beforeAll } from 'vitest';
import type { DeepPartial } from 'ts-essentials';
import { deepMergeObject } from '../../../src/util/objects';


/**
* Temporarily sets the config to the given value for all tests in the suite.
*/
export function useConfigForTest(config: DeepPartial<FlowrConfigOptions>): void {
const currentConfig = getConfig();
beforeAll(() => {
setConfig(deepMergeObject(currentConfig, config) as FlowrConfigOptions);
});
afterAll(() => {
setConfig(currentConfig);
});
}
19 changes: 17 additions & 2 deletions test/functionality/slicing/pointer-analysis/list-access.test.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import { describe, it } from 'vitest';
import { assertSliced, withShell } from '../../_helper/shell';
import { label } from '../../_helper/label';
import { useConfigForTest } from '../../_helper/config';

describe.sequential('List access', withShell(shell => {
const basicCapabilities = ['name-normal', 'function-calls', 'named-arguments', 'dollar-access', 'subsetting'] as const;
useConfigForTest({ solver: { pointerTracking: true } });
describe('Simple access', () => {
assertSliced(label('List with single element', basicCapabilities), shell,
'person <- list(name = "John")\nprint(person$name)',
Expand Down Expand Up @@ -123,7 +125,7 @@ result <- person$age`,
`person <- list(age = 24, name = "John", is_male = TRUE)
result <- person$age`,
);

describe('Access within conditionals', () => {
assertSliced(label('Only a potential overwrite', basicCapabilities),
shell,
Expand Down Expand Up @@ -175,7 +177,7 @@ print(wrapper$person$age)`);
});
});
});

describe('Nested lists', () => {
assertSliced(label('When index of nested list is overwritten, then overwrite is also in slice', basicCapabilities),
shell,
Expand Down Expand Up @@ -382,4 +384,17 @@ result <- person$name`,
);
});
});

describe('Config flag', () => {
useConfigForTest({ solver: { pointerTracking: false } });
assertSliced(label('When flag is false, then list access is not in slice', ['call-normal']), shell,
`person <- list(age = 24, name = "John")
person$name <- "Jane"
person$age <- 23
print(person$name)`, ['4@print'], `person <- list(age = 24, name = "John")
person$name <- "Jane"
person$age <- 23
print(person$name)`
);
});
}));

0 comments on commit 0ca7566

Please sign in to comment.