Skip to content

Commit

Permalink
Merge branch 'master' into issue.196
Browse files Browse the repository at this point in the history
  • Loading branch information
blitz-1306 authored Apr 6, 2023
2 parents 77d742a + b824da1 commit 7b1abed
Show file tree
Hide file tree
Showing 6 changed files with 212 additions and 202 deletions.
285 changes: 144 additions & 141 deletions package-lock.json

Large diffs are not rendered by default.

12 changes: 6 additions & 6 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "solc-typed-ast",
"version": "13.0.3",
"version": "14.0.0",
"description": "A TypeScript library providing a normalized typed Solidity AST along with the utilities necessary to generate the AST (from Solc) and traverse/manipulate it.",
"keywords": [],
"files": [
Expand Down Expand Up @@ -41,11 +41,11 @@
"devDependencies": {
"@types/fs-extra": "^11.0.1",
"@types/mocha": "^10.0.1",
"@types/node": "^16.18.21",
"@types/node": "^16.18.23",
"@types/semver": "^7.3.13",
"@typescript-eslint/eslint-plugin": "^5.56.0",
"@typescript-eslint/parser": "^5.56.0",
"eslint": "^8.36.0",
"@typescript-eslint/eslint-plugin": "^5.57.1",
"@typescript-eslint/parser": "^5.57.1",
"eslint": "^8.37.0",
"eslint-config-prettier": "^8.8.0",
"eslint-plugin-prettier": "^4.2.1",
"expect": "^29.5.0",
Expand All @@ -56,7 +56,7 @@
"ts-node": "^10.9.1",
"ts-pegjs": "^3.1.0",
"typedoc": "^0.23.28",
"typescript": "^5.0.2"
"typescript": "^5.0.3"
},
"homepage": "https://consensys.github.io/solc-typed-ast",
"bugs": "https://github.com/ConsenSys/solc-typed-ast/issues",
Expand Down
89 changes: 40 additions & 49 deletions src/types/eval_const.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,9 @@ import {
VariableDeclaration
} from "../ast";
import { assert, pp } from "../misc";
import { BINARY_OPERATOR_GROUPS, SUBDENOMINATION_MULTIPLIERS } from "./utils";
import { InferType } from "./infer";
import { IntType } from "./ast";
import { InferType } from "./infer";
import { BINARY_OPERATOR_GROUPS, SUBDENOMINATION_MULTIPLIERS, clampIntToType } from "./utils";
/**
* Tune up precision of decimal values to follow Solidity behavior.
* Be careful with precision - setting it to large values causes NodeJS to crash.
Expand Down Expand Up @@ -360,9 +360,9 @@ export function evalLiteral(node: Literal): Value {
}
}

export function evalUnary(node: UnaryOperation): Value {
export function evalUnary(node: UnaryOperation, inference: InferType): Value {
try {
return evalUnaryImpl(node.operator, evalConstantExpr(node.vSubExpression));
return evalUnaryImpl(node.operator, evalConstantExpr(node.vSubExpression, inference));
} catch (e: unknown) {
if (e instanceof EvalError && e.expr === undefined) {
e.expr = node;
Expand All @@ -372,12 +372,12 @@ export function evalUnary(node: UnaryOperation): Value {
}
}

export function evalBinary(node: BinaryOperation): Value {
export function evalBinary(node: BinaryOperation, inference: InferType): Value {
try {
return evalBinaryImpl(
node.operator,
evalConstantExpr(node.vLeftExpression),
evalConstantExpr(node.vRightExpression)
evalConstantExpr(node.vLeftExpression, inference),
evalConstantExpr(node.vRightExpression, inference)
);
} catch (e: unknown) {
if (e instanceof EvalError && e.expr === undefined) {
Expand All @@ -388,14 +388,36 @@ export function evalBinary(node: BinaryOperation): Value {
}
}

export function evalFunctionCall(node: FunctionCall, inference: InferType): Value {
assert(
node.kind === FunctionCallKind.TypeConversion,
`Expected constant call to be a "{0}", but got "{1}" instead`,
FunctionCallKind.TypeConversion,
node.kind
);

const val = evalConstantExpr(node.vArguments[0], inference);

if (typeof val === "bigint" && node.vExpression instanceof ElementaryTypeNameExpression) {
const castExprT = inference.typeOfElementaryTypeNameExpression(node.vExpression);
const castT = castExprT.type;

if (castT instanceof IntType) {
return clampIntToType(val, castT);
}
}

return val;
}

/**
* Given a constant expression `expr` evaluate it to a concrete `Value`.
* If `expr` is not constant throw `NonConstantExpressionError`.
*
* TODO: The order of some operations changed in some version.
* So perhaps to be fully precise here we will need a compiler version too?
* @todo The order of some operations changed in some version.
* Current implementation does not yet take it into an account.
*/
export function evalConstantExpr(node: Expression): Value {
export function evalConstantExpr(node: Expression, inference: InferType): Value {
if (!isConstant(node)) {
throw new NonConstantExpressionError(node);
}
Expand All @@ -405,53 +427,33 @@ export function evalConstantExpr(node: Expression): Value {
}

if (node instanceof UnaryOperation) {
return evalUnary(node);
return evalUnary(node, inference);
}

if (node instanceof BinaryOperation) {
return evalBinary(node);
return evalBinary(node, inference);
}

if (node instanceof TupleExpression) {
return evalConstantExpr(node.vOriginalComponents[0] as Expression);
return evalConstantExpr(node.vOriginalComponents[0] as Expression, inference);
}

if (node instanceof Conditional) {
return evalConstantExpr(node.vCondition)
? evalConstantExpr(node.vTrueExpression)
: evalConstantExpr(node.vFalseExpression);
return evalConstantExpr(node.vCondition, inference)
? evalConstantExpr(node.vTrueExpression, inference)
: evalConstantExpr(node.vFalseExpression, inference);
}

if (node instanceof Identifier) {
const decl = node.vReferencedDeclaration;

if (decl instanceof VariableDeclaration) {
return evalConstantExpr(decl.vValue as Expression);
return evalConstantExpr(decl.vValue as Expression, inference);
}
}

if (node instanceof FunctionCall) {
assert(
node.kind === FunctionCallKind.TypeConversion,
`Expected constant call to be a type conversion not {0}`,
node.kind
);

const val = evalConstantExpr(node.vArguments[0]);

if (
typeof val === "bigint" &&
node.vExpression instanceof ElementaryTypeNameExpression &&
typeof node.vExpression.typeName == "string"
) {
const castT = InferType.elementaryTypeNameStringToTypeNode(node.vExpression.typeName);

if (castT instanceof IntType) {
return clampIntToType(val, castT);
}
}

return val;
return evalFunctionCall(node, inference);
}

/**
Expand All @@ -461,14 +463,3 @@ export function evalConstantExpr(node: Expression): Value {
*/
throw new EvalError(`Unable to evaluate constant expression ${pp(node)}`, node);
}

/**
* Helper to cast the bigint `val` to the `IntType` `type`.
*/
function clampIntToType(val: bigint, type: IntType): bigint {
const min = type.min();
const max = type.max();
const size = max - min + 1n;

return val < min ? ((val - max) % size) + max : ((val - min) % size) + min;
}
8 changes: 4 additions & 4 deletions src/types/infer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -498,7 +498,7 @@ export class InferType {
const b = this.typeOf(node.vRightExpression);

if (a instanceof NumericLiteralType && b instanceof NumericLiteralType) {
const res = evalConstantExpr(node);
const res = evalConstantExpr(node, this);

assert(
res instanceof Decimal || typeof res === "bigint",
Expand Down Expand Up @@ -1685,7 +1685,7 @@ export class InferType {
}

if (innerT instanceof NumericLiteralType) {
const res = evalConstantExpr(node);
const res = evalConstantExpr(node, this);

assert(
res instanceof Decimal || typeof res === "bigint",
Expand All @@ -1704,7 +1704,7 @@ export class InferType {
throw new Error(`NYI unary operator ${node.operator} in ${pp(node)}`);
}

typeOfElementaryTypeNameExpression(node: ElementaryTypeNameExpression): TypeNode {
typeOfElementaryTypeNameExpression(node: ElementaryTypeNameExpression): TypeNameType {
let innerT: TypeNode;

if (node.typeName instanceof TypeName) {
Expand Down Expand Up @@ -2235,7 +2235,7 @@ export class InferType {
let size: bigint | undefined;

if (node.vLength) {
const result = evalConstantExpr(node.vLength);
const result = evalConstantExpr(node.vLength, this);

assert(
typeof result === "bigint",
Expand Down
12 changes: 12 additions & 0 deletions src/types/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -550,6 +550,18 @@ export function smallestFittingType(...literals: bigint[]): IntType | undefined
return undefined;
}

/**
* Helper to cast the bigint `val` to the `IntType` `type`.
*/
export function clampIntToType(val: bigint, type: IntType): bigint {
const min = type.min();
const max = type.max();

const size = max - min + 1n;

return val < min ? ((val - max) % size) + max : ((val - min) % size) + min;
}

export function decimalToRational(d: Decimal): Rational {
if (!d.isFinite()) {
throw new Error(`Unexpected infinite rational ${d.toString()} in decimalToRational`);
Expand Down
8 changes: 6 additions & 2 deletions test/unit/types/eval_const.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ import {
evalConstantExpr,
Expression,
FunctionCallKind,
InferType,
isConstant,
LatestCompilerVersion,
LiteralKind,
Mutability,
StateVariableVisibility,
Expand Down Expand Up @@ -1228,9 +1230,11 @@ const cases: Array<[string, (factory: ASTNodeFactory) => Expression, boolean, Va

describe("Constant expression evaluator unit test (isConstant() + evalConstantExpr())", () => {
let factory: ASTNodeFactory;
let inference: InferType;

before(() => {
factory = new ASTNodeFactory();
inference = new InferType(LatestCompilerVersion);
});

for (const [name, exprBuilder, isConst, value] of cases) {
Expand All @@ -1240,9 +1244,9 @@ describe("Constant expression evaluator unit test (isConstant() + evalConstantEx
expect(isConstant(expr)).toEqual(isConst);

if (value === undefined) {
expect(() => evalConstantExpr(expr)).toThrow();
expect(() => evalConstantExpr(expr, inference)).toThrow();
} else {
expect(evalConstantExpr(expr)).toEqual(value);
expect(evalConstantExpr(expr, inference)).toEqual(value);
}
});
}
Expand Down

0 comments on commit 7b1abed

Please sign in to comment.