Skip to content

Commit

Permalink
feat(error): errors now point to the Node that caused or threw them. …
Browse files Browse the repository at this point in the history
…Errors caused by the evaluated code itself such as ThrowStatements will produce ThrownError objects that point to the original error as well as the Node that caused or threw it. Fixes #8
  • Loading branch information
wessberg committed Dec 30, 2018
1 parent 2e091c0 commit 134b8ef
Show file tree
Hide file tree
Showing 37 changed files with 137 additions and 68 deletions.
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export {EvaluationError} from "./interpreter/error/evaluation-error/evaluation-e
export {MissingCatchOrFinallyAfterTryError} from "./interpreter/error/missing-catch-or-finally-after-try-error/missing-catch-or-finally-after-try-error";
export {ModuleNotFoundError} from "./interpreter/error/module-not-found-error/module-not-found-error";
export {NotCallableError} from "./interpreter/error/not-callable-error/not-callable-error";
export {ThrownError} from "./interpreter/error/thrown-error/thrown-error";
export {PolicyError} from "./interpreter/error/policy-error/policy-error";
export {UndefinedIdentifierError} from "./interpreter/error/undefined-identifier-error/undefined-identifier-error";
export {UndefinedLeftValueError} from "./interpreter/error/undefined-left-value-error/undefined-left-value-error";
Expand Down
14 changes: 7 additions & 7 deletions src/interpreter/environment/create-sanitized-environment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import {isConsoleOperation} from "../policy/console/is-console-operation";
* @param {ICreateSanitizedEnvironmentOptions} options
* @return {IndexLiteral}
*/
export function createSanitizedEnvironment ({policy, env}: ICreateSanitizedEnvironmentOptions): IndexLiteral {
export function createSanitizedEnvironment ({policy, env, getCurrentNode}: ICreateSanitizedEnvironmentOptions): IndexLiteral {

const hook = (item: PolicyProxyHookOptions<any>) => {

Expand All @@ -31,27 +31,27 @@ export function createSanitizedEnvironment ({policy, env}: ICreateSanitizedEnvir
}

if (!policy.io.read && isIoRead(item)) {
throw new IoError({kind: "read"});
throw new IoError({kind: "read", node: getCurrentNode()});
}

if (!policy.io.write && isIoWrite(item)) {
throw new IoError({kind: "write"});
throw new IoError({kind: "write", node: getCurrentNode()});
}

if (!policy.process.exit && isProcessExitOperation(item)) {
throw new ProcessError({kind: "exit"});
throw new ProcessError({kind: "exit", node: getCurrentNode()});
}

if (!policy.process.exit && isProcessSpawnChildOperation(item)) {
throw new ProcessError({kind: "spawnChild"});
throw new ProcessError({kind: "spawnChild", node: getCurrentNode()});
}

if (!policy.network && isNetworkOperation(item)) {
throw new NetworkError({operation: stringifyPolicyTrapKindOnPath(item.kind, item.path)});
throw new NetworkError({operation: stringifyPolicyTrapKindOnPath(item.kind, item.path), node: getCurrentNode()});
}

if (policy.deterministic && isNonDeterministic(item)) {
throw new NonDeterministicError({operation: stringifyPolicyTrapKindOnPath(item.kind, item.path)});
throw new NonDeterministicError({operation: stringifyPolicyTrapKindOnPath(item.kind, item.path), node: getCurrentNode()});
}

return true;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import {IEvaluatePolicySanitized} from "../policy/i-evaluate-policy";
import {IndexLiteral} from "../literal/literal";
import {Node} from "typescript";

export interface ICreateSanitizedEnvironmentOptions {
policy: IEvaluatePolicySanitized;
env: IndexLiteral;
getCurrentNode (): Node;
}
9 changes: 8 additions & 1 deletion src/interpreter/error/evaluation-error/evaluation-error.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,18 @@
import {IEvaluationErrorOptions} from "./i-evaluation-error-options";
import {Node} from "typescript";

/**
* A Base class for EvaluationErrors
*/
export class EvaluationError extends Error {
constructor ({message}: IEvaluationErrorOptions = {}) {
/**
* The node that caused or thew the error
*/
public readonly node: Node;

constructor ({node, message}: IEvaluationErrorOptions) {
super(message);
Error.captureStackTrace(this, this.constructor);
this.node = node;
}
}
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
import {Node} from "typescript";

export interface IEvaluationErrorOptions {
node: Node;
message?: string;
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@ import {TryStatement} from "typescript";
import {IEvaluationErrorOptions} from "../evaluation-error/i-evaluation-error-options";

export interface IMissingCatchOrFinallyAfterTryErrorOptions extends IEvaluationErrorOptions {
statement: TryStatement;
node: TryStatement;
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,11 @@ import {IMissingCatchOrFinallyAfterTryErrorOptions} from "./i-missing-catch-or-f
export class MissingCatchOrFinallyAfterTryError extends EvaluationError {
/**
* The TryStatement that lacks a catch/finally block
* @type {TryStatement}
*/
public readonly statement: TryStatement;
public readonly node: TryStatement;

constructor ({statement, message = `Missing catch or finally after try`}: IMissingCatchOrFinallyAfterTryErrorOptions) {
super({message});
this.statement = statement;
constructor ({node, message = `Missing catch or finally after try`}: IMissingCatchOrFinallyAfterTryErrorOptions) {
super({node, message});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ export class ModuleNotFoundError extends EvaluationError {
*/
public readonly path: string;

constructor ({path, message = `Module '${path}' could not be resolved'`}: IModuleNotFoundErrorOptions) {
super({message});
constructor ({path, node, message = `Module '${path}' could not be resolved'`}: IModuleNotFoundErrorOptions) {
super({message, node});
this.path = path;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ export class NotCallableError extends EvaluationError {
*/
public readonly value: Literal;

constructor ({value, message = `${stringifyLiteral(value)} is not a function'`}: INotCallableErrorOptions) {
super({message});
constructor ({value, node, message = `${stringifyLiteral(value)} is not a function'`}: INotCallableErrorOptions) {
super({message, node});
this.value = value;
}
}
4 changes: 2 additions & 2 deletions src/interpreter/error/policy-error/io-error/io-error.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ export class IoError extends PolicyError {
*/
public readonly kind: keyof IEvaluateIOPolicy;

constructor ({kind, message = `${kind} operations are in violation of the policy`}: IIoErrorOptions) {
super({violation: "io", message});
constructor ({node, kind, message = `${kind} operations are in violation of the policy`}: IIoErrorOptions) {
super({violation: "io", message, node});
this.kind = kind;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ export class MaxOpDurationExceededError extends PolicyError {
*/
public readonly duration: number;

constructor ({duration, message = `Maximum operation duration exceeded: ${duration}`}: IMaxOpDurationExceededErrorOptions) {
super({violation: "maxOpDuration", message});
constructor ({duration, node, message = `Maximum operation duration exceeded: ${duration}`}: IMaxOpDurationExceededErrorOptions) {
super({violation: "maxOpDuration", message, node});
this.duration = duration;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ export class MaxOpsExceededError extends PolicyError {
*/
public readonly ops: number;

constructor ({ops, message = `Maximum ops exceeded: ${ops}`}: IMaxOpsExceededErrorOptions) {
super({violation: "maxOps", message});
constructor ({ops, node, message = `Maximum ops exceeded: ${ops}`}: IMaxOpsExceededErrorOptions) {
super({violation: "maxOps", message, node});
this.ops = ops;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ export class NetworkError extends PolicyError {
*/
public readonly operation: string;

constructor ({operation, message = `The operation: '${operation}' is performing network activity. That is in violation of the policy`}: INetworkErrorOptions) {
super({violation: "deterministic", message});
constructor ({operation, node, message = `The operation: '${operation}' is performing network activity. That is in violation of the policy`}: INetworkErrorOptions) {
super({violation: "deterministic", message, node});

this.operation = operation;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ export class NonDeterministicError extends PolicyError {
*/
public readonly operation: string;

constructor ({operation, message = `The operation: '${operation}' is nondeterministic. That is in violation of the policy`}: INonDeterministicErrorOptions) {
super({violation: "deterministic", message});
constructor ({operation, node, message = `The operation: '${operation}' is nondeterministic. That is in violation of the policy`}: INonDeterministicErrorOptions) {
super({violation: "deterministic", message, node});

this.operation = operation;
}
Expand Down
4 changes: 2 additions & 2 deletions src/interpreter/error/policy-error/policy-error.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ export class PolicyError extends EvaluationError {
*/
public readonly violation: keyof IEvaluatePolicySanitized;

constructor ({violation, message}: IPolicyErrorOptions) {
super({message: `[${violation}]: ${message}`});
constructor ({violation, node, message}: IPolicyErrorOptions) {
super({node, message: `[${violation}]: ${message}`});
this.violation = violation;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ export class ProcessError extends PolicyError {
*/
public readonly kind: keyof IEvaluateProcessPolicy;

constructor ({kind, message = `${kind} operations are in violation of the policy`}: IProcessErrorOptions) {
super({violation: "process", message});
constructor ({kind, node, message = `${kind} operations are in violation of the policy`}: IProcessErrorOptions) {
super({violation: "process", message, node});
this.kind = kind;
}
}
5 changes: 5 additions & 0 deletions src/interpreter/error/thrown-error/i-thrown-error-options.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import {IEvaluationErrorOptions} from "../evaluation-error/i-evaluation-error-options";

export interface IThrownErrorOptions extends IEvaluationErrorOptions {
originalError: Error;
}
18 changes: 18 additions & 0 deletions src/interpreter/error/thrown-error/thrown-error.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import {EvaluationError} from "../evaluation-error/evaluation-error";
import {IThrownErrorOptions} from "./i-thrown-error-options";

/**
* An Error that represents an error thrown from within the code that is being evaluated.
*/
export class ThrownError extends EvaluationError {
/**
* The originally thrown Error
* @type {Error}
*/
public readonly originalError: Error;

constructor ({originalError, node, message = originalError.message}: IThrownErrorOptions) {
super({message, node});
this.originalError = originalError;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@ import {Identifier} from "typescript";
import {IEvaluationErrorOptions} from "../evaluation-error/i-evaluation-error-options";

export interface IUndefinedIdentifierErrorOptions extends IEvaluationErrorOptions {
identifier: Identifier;
node: Identifier;
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,11 @@ import {IUndefinedIdentifierErrorOptions} from "./i-undefined-identifier-error-o
export class UndefinedIdentifierError extends EvaluationError {
/**
* The identifier that is undefined in the context that created this error
* @type {Identifier}
*/
public readonly identifier: Identifier;
public readonly node: Identifier;

constructor ({identifier, message = `'${identifier.text}' is not defined'`}: IUndefinedIdentifierErrorOptions) {
super({message});
this.identifier = identifier;
constructor ({node, message = `'${node.text}' is not defined'`}: IUndefinedIdentifierErrorOptions) {
super({message, node});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {IUndefinedLeftValueErrorOptions} from "./i-undefined-left-value-error-op
*/
export class UndefinedLeftValueError extends EvaluationError {

constructor ({message = `'No leftValue could be determined'`}: IUndefinedLeftValueErrorOptions = {}) {
super({message});
constructor ({node, message = `'No leftValue could be determined'`}: IUndefinedLeftValueErrorOptions) {
super({message, node});
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
import {Node} from "typescript";
import {IEvaluationErrorOptions} from "../evaluation-error/i-evaluation-error-options";

export interface IUnexpectedNodeErrorOptions extends IEvaluationErrorOptions {
node: Node;
}
Original file line number Diff line number Diff line change
@@ -1,18 +1,13 @@
import {Node, SyntaxKind} from "typescript";
import {SyntaxKind} from "typescript";
import {EvaluationError} from "../evaluation-error/evaluation-error";
import {IUnexpectedNodeErrorOptions} from "./i-unexpected-node-error-options";

/**
* An Error that can be thrown when an unexpected node is encountered
*/
export class UnexpectedNodeError extends EvaluationError {
/**
* The node that was unexpected in the context that spawned this error
*/
public readonly node: Node;

constructor ({node, message = `Unexpected Node: '${SyntaxKind[node.kind]}'`}: IUnexpectedNodeErrorOptions) {
super({message});
this.node = node;
super({message, node});
}
}
34 changes: 32 additions & 2 deletions src/interpreter/evaluate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ import {isDeclaration} from "./util/declaration/is-declaration";
import {UnexpectedNodeError} from "./error/unexpected-node-error/unexpected-node-error";
import {IEvaluatePolicySanitized} from "./policy/i-evaluate-policy";
import {EnvironmentPresetKind} from "./environment/environment-preset-kind";
import {Node} from "typescript";
import {EvaluationError} from "./error/evaluation-error/evaluation-error";
import {ThrownError} from "./error/thrown-error/thrown-error";

/**
* Will get a literal value for the given Expression, ExpressionStatement, or Declaration.
Expand Down Expand Up @@ -67,11 +70,34 @@ export function evaluate ({
}
};

// Prepare a reference to the Node that is currently being evaluated
let currentNode: Node = node;

// Prepare a logger
const logger = new Logger(logLevel);
const initialEnvironment = createLexicalEnvironment({preset, extra}, policy);

// Prepare the initial environment
const initialEnvironment = createLexicalEnvironment({
inputEnvironment: {
preset,
extra
},
policy,
getCurrentNode: () => currentNode
});

// Prepare a Stack
const stack: Stack = createStack();
const nodeEvaluator = createNodeEvaluator({policy, typeChecker, logger, stack, reporting});

// Prepare a NodeEvaluator
const nodeEvaluator = createNodeEvaluator({
policy,
typeChecker,
logger,
stack,
reporting,
nextNode: nextNode => currentNode = nextNode
});

try {
let value: Literal;
Expand Down Expand Up @@ -103,6 +129,10 @@ export function evaluate ({
value
};
} catch (reason) {
// If the Error hasn't been wrapped or wasn't thrown internally, wrap it in a ThrownError
if (!(reason instanceof EvaluationError)) {
reason = new ThrownError({originalError: reason, node: currentNode});
}
return {
success: false,
reason
Expand Down
2 changes: 1 addition & 1 deletion src/interpreter/evaluator/evaluate-await-expression.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export function evaluateAwaitExpression ({node, environment, evaluate, policy, s
const timeout = policy.maxOpDuration === Infinity
? undefined
: setTimeout(() => {
throw new MaxOpDurationExceededError({duration: policy.maxOpDuration});
throw new MaxOpDurationExceededError({duration: policy.maxOpDuration, node});
}, policy.maxOpDuration);

const result = syncAwait(evaluate.expression(node.expression, environment, statementTraversalStack) as Promise<Literal>);
Expand Down
2 changes: 1 addition & 1 deletion src/interpreter/evaluator/evaluate-binary-expression.ts
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ export function evaluateBinaryExpression ({node, environment, evaluate, logger,
}

else {
throw new UndefinedLeftValueError();
throw new UndefinedLeftValueError({node: node.left});
}

// The return value of an assignment is always the assigned value
Expand Down
2 changes: 1 addition & 1 deletion src/interpreter/evaluator/evaluate-call-expression.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ export function evaluateCallExpression ({node, environment, evaluate, statementT
// Otherwise, assume that the expression still needs calling
else {
if (typeof expressionResult !== "function") {
throw new NotCallableError({value: expressionResult});
throw new NotCallableError({value: expressionResult, node: node.expression});
}

const value = expressionResult(...evaluatedArgs);
Expand Down
Loading

0 comments on commit 134b8ef

Please sign in to comment.