Skip to content

Commit

Permalink
Add circularity prevention to getLiteralValueAtPath
Browse files Browse the repository at this point in the history
  • Loading branch information
lukastaegert committed May 16, 2018
1 parent 6065388 commit e69fbf2
Show file tree
Hide file tree
Showing 19 changed files with 137 additions and 80 deletions.
9 changes: 9 additions & 0 deletions src/ast/ExecutionPathOptions.ts
Expand Up @@ -17,6 +17,7 @@ export enum OptionTypes {
IGNORE_RETURN_AWAIT_YIELD,
NODES_CALLED_AT_PATH_WITH_OPTIONS,
REPLACED_VARIABLE_INITS,
RETRIEVED_VALUE_NODES,
RETURN_EXPRESSIONS_ACCESSED_AT_PATH,
RETURN_EXPRESSIONS_ASSIGNED_AT_PATH,
RETURN_EXPRESSIONS_CALLED_AT_PATH
Expand Down Expand Up @@ -95,6 +96,10 @@ export class ExecutionPathOptions {
);
}

addRetrievedNodeValueAtPath(path: ObjectPath, node: ExpressionEntity) {
return this.setIn([OptionTypes.RETRIEVED_VALUE_NODES, node, ...path, RESULT_KEY], true);
}

getArgumentsVariables(): ExpressionEntity[] {
return <ExpressionEntity[]>(this.get(OptionTypes.ARGUMENTS_VARIABLES) || []);
}
Expand Down Expand Up @@ -136,6 +141,10 @@ export class ExecutionPathOptions {
);
}

hasNodeValueBeenRetrievedAtPath(path: ObjectPath, node: ExpressionEntity): boolean {
return this.optionValues.getIn([OptionTypes.RETRIEVED_VALUE_NODES, node, ...path, RESULT_KEY]);
}

hasReturnExpressionBeenAccessedAtPath(
path: ObjectPath,
callExpression: CallExpression | Property
Expand Down
6 changes: 3 additions & 3 deletions src/ast/nodes/BinaryExpression.ts
Expand Up @@ -37,12 +37,12 @@ export default class BinaryExpression extends NodeBase {
right: ExpressionNode;
operator: keyof typeof binaryOperators;

getLiteralValueAtPath(path: ObjectPath): LiteralValueOrUnknown {
getLiteralValueAtPath(path: ObjectPath, options: ExecutionPathOptions): LiteralValueOrUnknown {
if (path.length > 0) return UNKNOWN_VALUE;
const leftValue = this.left.getLiteralValueAtPath(EMPTY_PATH);
const leftValue = this.left.getLiteralValueAtPath(EMPTY_PATH, options);
if (leftValue === UNKNOWN_VALUE) return UNKNOWN_VALUE;

const rightValue = this.right.getLiteralValueAtPath(EMPTY_PATH);
const rightValue = this.right.getLiteralValueAtPath(EMPTY_PATH, options);
if (rightValue === UNKNOWN_VALUE) return UNKNOWN_VALUE;

const operatorFn = binaryOperators[this.operator];
Expand Down
32 changes: 17 additions & 15 deletions src/ast/nodes/ConditionalExpression.ts
@@ -1,5 +1,5 @@
import { ObjectPath, LiteralValueOrUnknown, UNKNOWN_VALUE, EMPTY_PATH } from '../values';
import { ExecutionPathOptions } from '../ExecutionPathOptions';
import { ExecutionPathOptions, NEW_EXECUTION_PATH } from '../ExecutionPathOptions';
import CallOptions from '../CallOptions';
import MagicString from 'magic-string';
import { ForEachReturnExpressionCallback, SomeReturnExpressionCallback } from './shared/Expression';
Expand All @@ -23,7 +23,7 @@ export default class ConditionalExpression extends NodeBase {
callback: ForEachReturnExpressionCallback,
options: ExecutionPathOptions
) {
const testValue = this.hasUnknownTestValue ? UNKNOWN_VALUE : this.getTestValue();
const testValue = this.hasUnknownTestValue ? UNKNOWN_VALUE : this.getTestValue(options);
if (testValue === UNKNOWN_VALUE || testValue) {
this.consequent.forEachReturnExpressionWhenCalledAtPath(path, callOptions, callback, options);
}
Expand All @@ -32,17 +32,17 @@ export default class ConditionalExpression extends NodeBase {
}
}

getLiteralValueAtPath(path: ObjectPath): LiteralValueOrUnknown {
const testValue = this.hasUnknownTestValue ? UNKNOWN_VALUE : this.getTestValue();
getLiteralValueAtPath(path: ObjectPath, options: ExecutionPathOptions): LiteralValueOrUnknown {
const testValue = this.hasUnknownTestValue ? UNKNOWN_VALUE : this.getTestValue(options);
if (testValue === UNKNOWN_VALUE) return UNKNOWN_VALUE;
return testValue
? this.consequent.getLiteralValueAtPath(path)
: this.alternate.getLiteralValueAtPath(path);
? this.consequent.getLiteralValueAtPath(path, options)
: this.alternate.getLiteralValueAtPath(path, options);
}

hasEffects(options: ExecutionPathOptions): boolean {
if (this.test.hasEffects(options)) return true;
const testValue = this.hasUnknownTestValue ? UNKNOWN_VALUE : this.getTestValue();
const testValue = this.hasUnknownTestValue ? UNKNOWN_VALUE : this.getTestValue(options);
if (testValue === UNKNOWN_VALUE) {
return this.consequent.hasEffects(options) || this.alternate.hasEffects(options);
}
Expand All @@ -51,7 +51,7 @@ export default class ConditionalExpression extends NodeBase {

hasEffectsWhenAccessedAtPath(path: ObjectPath, options: ExecutionPathOptions): boolean {
if (path.length === 0) return false;
const testValue = this.hasUnknownTestValue ? UNKNOWN_VALUE : this.getTestValue();
const testValue = this.hasUnknownTestValue ? UNKNOWN_VALUE : this.getTestValue(options);
if (testValue === UNKNOWN_VALUE) {
return (
this.consequent.hasEffectsWhenAccessedAtPath(path, options) ||
Expand All @@ -65,7 +65,7 @@ export default class ConditionalExpression extends NodeBase {

hasEffectsWhenAssignedAtPath(path: ObjectPath, options: ExecutionPathOptions): boolean {
if (path.length === 0) return true;
const testValue = this.hasUnknownTestValue ? UNKNOWN_VALUE : this.getTestValue();
const testValue = this.hasUnknownTestValue ? UNKNOWN_VALUE : this.getTestValue(options);
if (testValue === UNKNOWN_VALUE) {
return (
this.consequent.hasEffectsWhenAssignedAtPath(path, options) ||
Expand All @@ -82,7 +82,7 @@ export default class ConditionalExpression extends NodeBase {
callOptions: CallOptions,
options: ExecutionPathOptions
): boolean {
const testValue = this.hasUnknownTestValue ? UNKNOWN_VALUE : this.getTestValue();
const testValue = this.hasUnknownTestValue ? UNKNOWN_VALUE : this.getTestValue(options);
if (testValue === UNKNOWN_VALUE) {
return (
this.consequent.hasEffectsWhenCalledAtPath(path, callOptions, options) ||
Expand All @@ -101,7 +101,9 @@ export default class ConditionalExpression extends NodeBase {

include() {
this.included = true;
const testValue = this.hasUnknownTestValue ? UNKNOWN_VALUE : this.getTestValue();
const testValue = this.hasUnknownTestValue
? UNKNOWN_VALUE
: this.getTestValue(NEW_EXECUTION_PATH);
if (testValue === UNKNOWN_VALUE || this.test.shouldBeIncluded()) {
this.test.include();
this.consequent.include();
Expand All @@ -115,7 +117,7 @@ export default class ConditionalExpression extends NodeBase {

reassignPath(path: ObjectPath, options: ExecutionPathOptions) {
if (path.length > 0) {
const testValue = this.hasUnknownTestValue ? UNKNOWN_VALUE : this.getTestValue();
const testValue = this.hasUnknownTestValue ? UNKNOWN_VALUE : this.getTestValue(options);
if (testValue === UNKNOWN_VALUE || testValue) {
this.consequent.reassignPath(path, options);
}
Expand Down Expand Up @@ -151,7 +153,7 @@ export default class ConditionalExpression extends NodeBase {
predicateFunction: SomeReturnExpressionCallback,
options: ExecutionPathOptions
): boolean {
const testValue = this.hasUnknownTestValue ? UNKNOWN_VALUE : this.getTestValue();
const testValue = this.hasUnknownTestValue ? UNKNOWN_VALUE : this.getTestValue(options);
if (testValue === UNKNOWN_VALUE) {
return (
this.consequent.someReturnExpressionWhenCalledAtPath(
Expand Down Expand Up @@ -183,9 +185,9 @@ export default class ConditionalExpression extends NodeBase {
);
}

private getTestValue() {
private getTestValue(options: ExecutionPathOptions) {
if (this.hasUnknownTestValue) return UNKNOWN_VALUE;
const value = this.test.getLiteralValueAtPath(EMPTY_PATH);
const value = this.test.getLiteralValueAtPath(EMPTY_PATH, options);
if (value === UNKNOWN_VALUE) {
this.hasUnknownTestValue = true;
}
Expand Down
4 changes: 2 additions & 2 deletions src/ast/nodes/Identifier.ts
Expand Up @@ -72,9 +72,9 @@ export default class Identifier extends NodeBase {
}
}

getLiteralValueAtPath(path: ObjectPath): LiteralValueOrUnknown {
getLiteralValueAtPath(path: ObjectPath, options: ExecutionPathOptions): LiteralValueOrUnknown {
if (this.variable !== null) {
return this.variable.getLiteralValueAtPath(path);
return this.variable.getLiteralValueAtPath(path, options);
}
return UNKNOWN_VALUE;
}
Expand Down
14 changes: 8 additions & 6 deletions src/ast/nodes/IfStatement.ts
Expand Up @@ -3,7 +3,7 @@ import { ExpressionNode, StatementBase, StatementNode } from './shared/Node';
import MagicString from 'magic-string';
import * as NodeType from './NodeType';
import { RenderOptions } from '../../utils/renderHelpers';
import { ExecutionPathOptions } from '../ExecutionPathOptions';
import { ExecutionPathOptions, NEW_EXECUTION_PATH } from '../ExecutionPathOptions';

export default class IfStatement extends StatementBase {
type: NodeType.tIfStatement;
Expand All @@ -15,7 +15,7 @@ export default class IfStatement extends StatementBase {

hasEffects(options: ExecutionPathOptions): boolean {
if (this.test.hasEffects(options)) return true;
const testValue = this.hasUnknownTestValue ? UNKNOWN_VALUE : this.getTestValue();
const testValue = this.hasUnknownTestValue ? UNKNOWN_VALUE : this.getTestValue(options);
if (testValue === UNKNOWN_VALUE) {
return (
this.consequent.hasEffects(options) ||
Expand All @@ -29,7 +29,9 @@ export default class IfStatement extends StatementBase {

include() {
this.included = true;
const testValue = this.hasUnknownTestValue ? UNKNOWN_VALUE : this.getTestValue();
const testValue = this.hasUnknownTestValue
? UNKNOWN_VALUE
: this.getTestValue(NEW_EXECUTION_PATH);
if (testValue === UNKNOWN_VALUE || this.test.shouldBeIncluded()) {
this.test.include();
}
Expand All @@ -53,7 +55,7 @@ export default class IfStatement extends StatementBase {
// Note that unknown test values are always included
const testValue = this.hasUnknownTestValue
? UNKNOWN_VALUE
: this.test.getLiteralValueAtPath(EMPTY_PATH);
: this.test.getLiteralValueAtPath(EMPTY_PATH, NEW_EXECUTION_PATH);
if (
!this.test.included &&
(testValue ? this.alternate === null || !this.alternate.included : !this.consequent.included)
Expand Down Expand Up @@ -83,9 +85,9 @@ export default class IfStatement extends StatementBase {
}
}

private getTestValue() {
private getTestValue(options: ExecutionPathOptions) {
if (this.hasUnknownTestValue) return UNKNOWN_VALUE;
const value = this.test.getLiteralValueAtPath(EMPTY_PATH);
const value = this.test.getLiteralValueAtPath(EMPTY_PATH, options);
if (value === UNKNOWN_VALUE) {
this.hasUnknownTestValue = true;
}
Expand Down
4 changes: 2 additions & 2 deletions src/ast/nodes/Literal.ts
Expand Up @@ -35,14 +35,14 @@ export default class Literal<T = LiteralValue> extends NodeBase {
return <any>this.value;
}

hasEffectsWhenAccessedAtPath(path: ObjectPath, _options: ExecutionPathOptions) {
hasEffectsWhenAccessedAtPath(path: ObjectPath) {
if (this.value === null) {
return path.length > 0;
}
return path.length > 1;
}

hasEffectsWhenAssignedAtPath(path: ObjectPath, _options: ExecutionPathOptions) {
hasEffectsWhenAssignedAtPath(path: ObjectPath) {
return path.length > 0;
}

Expand Down
30 changes: 16 additions & 14 deletions src/ast/nodes/LogicalExpression.ts
@@ -1,6 +1,6 @@
import { ObjectPath, LiteralValueOrUnknown, UNKNOWN_VALUE, EMPTY_PATH } from '../values';
import CallOptions from '../CallOptions';
import { ExecutionPathOptions } from '../ExecutionPathOptions';
import { ExecutionPathOptions, NEW_EXECUTION_PATH } from '../ExecutionPathOptions';
import { ForEachReturnExpressionCallback, SomeReturnExpressionCallback } from './shared/Expression';
import * as NodeType from './NodeType';
import { ExpressionNode, NodeBase } from './shared/Node';
Expand All @@ -26,7 +26,7 @@ export default class LogicalExpression extends NodeBase {
callback: ForEachReturnExpressionCallback,
options: ExecutionPathOptions
) {
const leftValue = this.hasUnknownLeftValue ? UNKNOWN_VALUE : this.getLeftValue();
const leftValue = this.hasUnknownLeftValue ? UNKNOWN_VALUE : this.getLeftValue(options);
if (leftValue === UNKNOWN_VALUE) {
this.left.forEachReturnExpressionWhenCalledAtPath(path, callOptions, callback, options);
this.right.forEachReturnExpressionWhenCalledAtPath(path, callOptions, callback, options);
Expand All @@ -37,16 +37,16 @@ export default class LogicalExpression extends NodeBase {
}
}

getLiteralValueAtPath(path: ObjectPath): LiteralValueOrUnknown {
const leftValue = this.hasUnknownLeftValue ? UNKNOWN_VALUE : this.getLeftValue();
getLiteralValueAtPath(path: ObjectPath, options: ExecutionPathOptions): LiteralValueOrUnknown {
const leftValue = this.hasUnknownLeftValue ? UNKNOWN_VALUE : this.getLeftValue(options);
if (leftValue === UNKNOWN_VALUE) return UNKNOWN_VALUE;
if (this.isOrExpression ? leftValue : !leftValue) return leftValue;
return this.right.getLiteralValueAtPath(path);
return this.right.getLiteralValueAtPath(path, options);
}

hasEffects(options: ExecutionPathOptions): boolean {
if (this.left.hasEffects(options)) return true;
const leftValue = this.hasUnknownLeftValue ? UNKNOWN_VALUE : this.getLeftValue();
const leftValue = this.hasUnknownLeftValue ? UNKNOWN_VALUE : this.getLeftValue(options);
return (
(leftValue === UNKNOWN_VALUE || (this.isOrExpression ? !leftValue : leftValue)) &&
this.right.hasEffects(options)
Expand All @@ -55,7 +55,7 @@ export default class LogicalExpression extends NodeBase {

hasEffectsWhenAccessedAtPath(path: ObjectPath, options: ExecutionPathOptions): boolean {
if (path.length === 0) return false;
const leftValue = this.hasUnknownLeftValue ? UNKNOWN_VALUE : this.getLeftValue();
const leftValue = this.hasUnknownLeftValue ? UNKNOWN_VALUE : this.getLeftValue(options);
if (leftValue === UNKNOWN_VALUE) {
return (
this.left.hasEffectsWhenAccessedAtPath(path, options) ||
Expand All @@ -71,7 +71,7 @@ export default class LogicalExpression extends NodeBase {

hasEffectsWhenAssignedAtPath(path: ObjectPath, options: ExecutionPathOptions): boolean {
if (path.length === 0) return true;
const leftValue = this.hasUnknownLeftValue ? UNKNOWN_VALUE : this.getLeftValue();
const leftValue = this.hasUnknownLeftValue ? UNKNOWN_VALUE : this.getLeftValue(options);
if (leftValue === UNKNOWN_VALUE) {
return (
this.left.hasEffectsWhenAssignedAtPath(path, options) ||
Expand All @@ -90,7 +90,7 @@ export default class LogicalExpression extends NodeBase {
callOptions: CallOptions,
options: ExecutionPathOptions
): boolean {
const leftValue = this.hasUnknownLeftValue ? UNKNOWN_VALUE : this.getLeftValue();
const leftValue = this.hasUnknownLeftValue ? UNKNOWN_VALUE : this.getLeftValue(options);
if (leftValue === UNKNOWN_VALUE) {
return (
this.left.hasEffectsWhenCalledAtPath(path, callOptions, options) ||
Expand All @@ -106,7 +106,9 @@ export default class LogicalExpression extends NodeBase {

include() {
this.included = true;
const leftValue = this.hasUnknownLeftValue ? UNKNOWN_VALUE : this.getLeftValue();
const leftValue = this.hasUnknownLeftValue
? UNKNOWN_VALUE
: this.getLeftValue(NEW_EXECUTION_PATH);
if (
leftValue === UNKNOWN_VALUE ||
(this.isOrExpression ? leftValue : !leftValue) ||
Expand All @@ -127,7 +129,7 @@ export default class LogicalExpression extends NodeBase {

reassignPath(path: ObjectPath, options: ExecutionPathOptions) {
if (path.length > 0) {
const leftValue = this.hasUnknownLeftValue ? UNKNOWN_VALUE : this.getLeftValue();
const leftValue = this.hasUnknownLeftValue ? UNKNOWN_VALUE : this.getLeftValue(options);
if (leftValue === UNKNOWN_VALUE) {
this.left.reassignPath(path, options);
this.right.reassignPath(path, options);
Expand Down Expand Up @@ -165,7 +167,7 @@ export default class LogicalExpression extends NodeBase {
predicateFunction: SomeReturnExpressionCallback,
options: ExecutionPathOptions
): boolean {
const leftValue = this.hasUnknownLeftValue ? UNKNOWN_VALUE : this.getLeftValue();
const leftValue = this.hasUnknownLeftValue ? UNKNOWN_VALUE : this.getLeftValue(options);
if (leftValue === UNKNOWN_VALUE) {
return (
this.left.someReturnExpressionWhenCalledAtPath(
Expand Down Expand Up @@ -199,9 +201,9 @@ export default class LogicalExpression extends NodeBase {
);
}

private getLeftValue() {
private getLeftValue(options: ExecutionPathOptions) {
if (this.hasUnknownLeftValue) return UNKNOWN_VALUE;
const value = this.left.getLiteralValueAtPath(EMPTY_PATH);
const value = this.left.getLiteralValueAtPath(EMPTY_PATH, options);
if (value === UNKNOWN_VALUE) {
this.hasUnknownLeftValue = true;
}
Expand Down

0 comments on commit e69fbf2

Please sign in to comment.