Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

(WIP) Add support for Yul AST nodes (addresses #163) #166

Open
wants to merge 28 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
693d1ac
Initial yul class definitions
Nov 7, 2022
143a7a2
initial (untested) modern processors for yul
Nov 7, 2022
7b6b668
add YulLiteralKind
Nov 7, 2022
1c9703a
make documentation methods generic to avoid type errors on ASTNodeWit…
Nov 7, 2022
c7d8b6d
partially implement new yul ast writers
Nov 7, 2022
2fd74a2
Add arg extractors for yul nodes
Nov 8, 2022
9fb5efe
Add make functions for Yul nodes
d1ll0n Nov 10, 2022
3fc2f9f
Don't try to convert nonexistent AST nodes
d1ll0n Nov 11, 2022
2e37b2f
allow empty parameter lists, disallow empty block
d1ll0n Nov 11, 2022
7a6e93d
YulNode -> YulBlock, add documentation
d1ll0n Nov 11, 2022
d0d3103
Add isYulASTNode
d1ll0n Nov 11, 2022
e7bc8ca
Add temporary(?) patch for yul node IDs.
d1ll0n Nov 11, 2022
8e7e059
only lookup standard AST nodes when testing total node count
d1ll0n Nov 11, 2022
a8167f3
add deepFindIn
d1ll0n Nov 11, 2022
18e4ec1
Partial patch for node formatter
d1ll0n Nov 11, 2022
06866c0
Implement all remaining ASTNodeWriter classes for Yul and mark old ve…
d1ll0n Nov 11, 2022
c2fb88d
Add yul export to index
d1ll0n Nov 11, 2022
e5b975e
Remove unused variable
d1ll0n Nov 11, 2022
1e45bec
Add make functions for yul identifiers and function calls
d1ll0n Nov 15, 2022
443696a
Add yul builtin function type and function type, as well as u256 defa…
d1ll0n Nov 17, 2022
1452d1d
Add yulTypes with only u256
d1ll0n Nov 17, 2022
348fb4d
Add yul builtin functions
d1ll0n Nov 17, 2022
5e8f01f
Merge branch 'yul-typed-ast' of github.com:d1ll0n/solc-typed-ast into…
d1ll0n Nov 17, 2022
7402bc6
Add isInstanceOf utility function
d1ll0n Nov 17, 2022
e8279d4
Add implementation index
d1ll0n Nov 17, 2022
65018da
Add isPure to yul builtins
d1ll0n Nov 17, 2022
1e5a09c
Add support for yul constant evaluation
d1ll0n Nov 17, 2022
692496f
Add tests for yul constant evaluation
d1ll0n Nov 17, 2022
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
393 changes: 334 additions & 59 deletions src/ast/ast_node_factory.ts

Large diffs are not rendered by default.

5 changes: 4 additions & 1 deletion src/ast/ast_node_formatter.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { ASTContext, ASTNode } from "./";
import { ASTContext, ASTNode, YulASTNode, YulASTNodeWithChildren } from "./";

const INDENT = " ".repeat(4);
const SKIP = new Set(["requiredContext", "raw", "ownChildren"]);
Expand Down Expand Up @@ -82,6 +82,9 @@ export class ASTNodeFormatter {
}

private formatNode(node: ASTNode, level: number, depth: number): string {
if (node instanceof YulASTNode || node instanceof YulASTNodeWithChildren) {
return this.formatValue(node.raw);
}
const output = [];
const value = this.formatNodeValue(node);

Expand Down
27 changes: 27 additions & 0 deletions src/ast/ast_reader.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
// import { deepFindIn } from "../misc";
import { ASTNode, ASTNodeConstructor } from "./ast_node";
import { SourceUnit } from "./implementation/meta/source_unit";
import { isYulASTNode } from "./implementation/yul";
import { LegacyConfiguration } from "./legacy";
import { ModernConfiguration } from "./modern";
import { DefaultNodePostprocessorList } from "./postprocessing";
Expand Down Expand Up @@ -45,6 +47,12 @@ export class ASTContext {
*/
id = contextIdSequence.next().value;

/**
* Temporary workaround
*/
readonly yulIdStart = 1e5;
lastYulId = this.yulIdStart;
Comment on lines +50 to +54
Copy link
Contributor

@blitz-1306 blitz-1306 Nov 21, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another option here is to consider using second standalone ASTContext for Yul nodes...

It seems a bit more clear and sound, however it would have more impact on code overall. Some pros/cons:

  1. It would be easier to keep it clean, as Solidity AST will not conflict with Yul AST references ever.
  2. There would would be no need to have hacks like 1e5, as ID space is separated.
  3. ASTReader and ASTNodeFactory would have to track two different AST contexts. Maybe YulNodes would also be affected if they refer to any ASTNodes as referenced declarations...

Unsure about consequences here. It may look cleaner but may also be impractical in the end. Still, I guess it may worth an attempt to see what would happend.


/**
* Map from ID number to the `AST` node with same ID in tree
*/
Expand All @@ -61,6 +69,9 @@ export class ASTContext {
let last = 0;

for (const id of this.map.keys()) {
if (id >= this.yulIdStart) {
continue;
}
if (id > last) {
last = id;
}
Expand All @@ -75,6 +86,9 @@ export class ASTContext {

register(...nodes: ASTNode[]): void {
for (const node of nodes) {
if (node.id === undefined && isYulASTNode(node)) {
node.id = ++this.lastYulId;
}
if (this.map.has(node.id)) {
throw new Error(`The id ${node.id} is already taken for the context`);
}
Expand Down Expand Up @@ -190,6 +204,19 @@ export class ASTReader {
const rootNodeTypeName = "SourceUnit";
const result: SourceUnit[] = [];

// const nodes = entries.map((e) => e[1]);
// const hasAssemblyAST = deepFindIn(
// nodes,
// "nodeType",
// (node: any) => node.nodeType.startsWith("Yul"),
// true
// );
// if (hasAssemblyAST) {
// const ids = deepFindIn(nodes, "id", (id) => typeof id === "number");
// const lastId = Math.max(...ids);
// this.context.lastOriginalId = lastId;
// }

for (const [key, content] of entries) {
let ast;

Expand Down
6 changes: 6 additions & 0 deletions src/ast/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,12 @@ export enum LiteralKind {
UnicodeString = "unicodeString"
}

export enum YulLiteralKind {
Number = "number",
Bool = "bool",
String = "string"
}

export enum EtherUnit {
Wei = "wei",
GWei = "gwei",
Expand Down
22 changes: 11 additions & 11 deletions src/ast/documentation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ export interface WithDanglingDocs {
danglingDocString?: string;
}

export function getDocumentation(
node: WithPrecedingDocs & ASTNodeWithChildren<ASTNode>
export function getDocumentation<T extends ASTNode = ASTNode>(
node: WithPrecedingDocs & ASTNodeWithChildren<T>
): string | StructuredDocumentation | undefined {
if (node.docString !== undefined) {
return node.docString;
Expand Down Expand Up @@ -50,8 +50,8 @@ export function getDocumentation(
return undefined;
}

export function setDocumentation(
node: WithPrecedingDocs & ASTNodeWithChildren<ASTNode>,
export function setDocumentation<T extends ASTNode = ASTNode>(
node: WithPrecedingDocs & ASTNodeWithChildren<T>,
value: string | StructuredDocumentation | undefined
): void {
const old = node.documentation;
Expand All @@ -61,22 +61,22 @@ export function setDocumentation(

if (old instanceof StructuredDocumentation) {
if (value !== old) {
node.replaceChild(value, old);
node.replaceChild(value as any, old as any);
}
} else {
node.insertAtBeginning(value);
node.insertAtBeginning(value as any);
}
} else {
if (old instanceof StructuredDocumentation) {
node.removeChild(old);
node.removeChild(old as any);
}

node.docString = value;
}
}

export function getDanglingDocumentation(
node: WithDanglingDocs & ASTNodeWithChildren<ASTNode>
export function getDanglingDocumentation<T extends ASTNode = ASTNode>(
node: WithDanglingDocs & ASTNodeWithChildren<T>
): string | StructuredDocumentation | undefined {
if (node.danglingDocString !== undefined) {
return node.danglingDocString;
Expand Down Expand Up @@ -104,8 +104,8 @@ export function getDanglingDocumentation(
return undefined;
}

export function setDanglingDocumentation(
node: WithDanglingDocs & ASTNodeWithChildren<ASTNode>,
export function setDanglingDocumentation<T extends ASTNode = ASTNode>(
node: WithDanglingDocs & ASTNodeWithChildren<T>,
value: string | StructuredDocumentation | undefined
): void {
const old = node.danglingDocumentation;
Expand Down
6 changes: 6 additions & 0 deletions src/ast/implementation/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export * from "./declaration";
export * from "./expression";
export * from "./meta";
export * from "./statement";
export * from "./type";
export * from "./yul";
11 changes: 9 additions & 2 deletions src/ast/implementation/statement/inline_assembly.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { ASTNode } from "../../ast_node";
import { StructuredDocumentation } from "../meta";
import { YulBlock } from "../yul";
import { Statement } from "./statement";

export interface YulNode {
Expand All @@ -12,7 +14,7 @@ export class InlineAssembly extends Statement {
externalReferences: any[];

operations?: string;
yul?: YulNode;
yul?: YulBlock;

flags?: string[];
evmVersion?: string;
Expand All @@ -22,7 +24,7 @@ export class InlineAssembly extends Statement {
src: string,
externalReferences: any[],
operations?: string,
yul?: YulNode,
yul?: YulBlock,
flags?: string[],
evmVersion?: string,
documentation?: string | StructuredDocumentation,
Expand All @@ -35,5 +37,10 @@ export class InlineAssembly extends Statement {
this.yul = yul;
this.flags = flags;
this.evmVersion = evmVersion;
this.acceptChildren();
}

get children(): ASTNode[] {
return this.pickNodes(this.documentation, this.yul);
}
}
5 changes: 5 additions & 0 deletions src/ast/implementation/yul/expression/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export * from "./yul_expression";
export * from "./yul_function_call";
export * from "./yul_literal";
export * from "./yul_identifier";
export * from "./yul_typed_name";
3 changes: 3 additions & 0 deletions src/ast/implementation/yul/expression/yul_expression.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { YulASTNode } from "../yul_ast_node";

export class YulExpression extends YulASTNode {}
56 changes: 56 additions & 0 deletions src/ast/implementation/yul/expression/yul_function_call.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { ExternalReferenceType } from "../../../constants";
import { YulIdentifier } from "./yul_identifier";
import { YulExpression } from "./yul_expression";
import { YulFunctionDefinition } from "../statement/yul_function_definition";
import { YulASTNode } from "../yul_ast_node";

export class YulFunctionCall extends YulExpression {
/**
* YulIdentifier that defines the callee
*/
vFunctionName: YulIdentifier;

/**
* Call arguments, e.g array with `1` and `2` expressions in `foo(1, 2)`
*/
vArguments: YulExpression[];

constructor(
id: number,
src: string,
functionName: YulIdentifier,
args: YulExpression[],
raw?: any
) {
super(id, src, raw);
this.vFunctionName = functionName;
this.vArguments = args;

this.acceptChildren();
}

get children(): readonly YulASTNode[] {
return this.pickNodes(this.vFunctionName, this.vArguments);
}

/**
* Identifier of the function name, e.g. `sha3(...)`
*/
get vIdentifier(): string {
return this.vFunctionName.name;
}

/**
* Solidity builtin or user-defined function
*/
get vFunctionCallType(): ExternalReferenceType {
return this.vFunctionName.vIdentifierType;
}

/**
* Called function definition reference
*/
get vReferencedDeclaration(): YulFunctionDefinition | undefined {
return this.vFunctionName.vReferencedDeclaration as YulFunctionDefinition;
}
}
52 changes: 52 additions & 0 deletions src/ast/implementation/yul/expression/yul_identifier.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { ASTNode } from "../../../ast_node";
import { ExternalReferenceType } from "../../../constants";
import { YulExpression } from "./yul_expression";

export class YulIdentifier extends YulExpression {
/**
* Name of the identifier
*/
name: string;

/**
* Id of the referenced declaration
*/
referencedDeclaration: number;

constructor(id: number, src: string, name: string, referencedDeclaration?: number, raw?: any) {
super(id, src, raw);

this.name = name;
this.referencedDeclaration = referencedDeclaration ?? -1;
}

/**
* Attribute to access the converted referenced declaration.
*
* Is `undefined` when this is a Solidity internal identifier.
*/
get vReferencedDeclaration(): ASTNode | undefined {
return this.requiredContext.locate(this.referencedDeclaration);
}

set vReferencedDeclaration(value: ASTNode | undefined) {
if (value === undefined) {
this.referencedDeclaration = -1;
} else {
if (!this.requiredContext.contains(value)) {
throw new Error(`Node ${value.type}#${value.id} not belongs to a current context`);
}

this.referencedDeclaration = value.id;
}
}

/**
* Solidity builtin or user-defined reference
*/
get vIdentifierType(): ExternalReferenceType {
return this.vReferencedDeclaration
? ExternalReferenceType.UserDefined
: ExternalReferenceType.Builtin;
}
}
41 changes: 41 additions & 0 deletions src/ast/implementation/yul/expression/yul_literal.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { YulLiteralKind } from "../../../constants";
import { YulExpression } from "./yul_expression";

export class YulLiteral extends YulExpression {
/**
* The type of literal: `number`, `string` or `bool`
*/
kind: YulLiteralKind;

/**
* Hexadecimal representation of value of the literal symbol
*/
hexValue: string;

/**
* Value of the literal symbol
*/
value: string;

/**
* Yul type string, e.g.u256
*/
typeString: string;

constructor(
id: number,
src: string,
kind: YulLiteralKind,
value: string,
hexValue: string,
typeString = "",
raw?: any
) {
super(id, src, raw);

this.kind = kind;
this.value = value;
this.hexValue = hexValue;
this.typeString = typeString;
}
}
20 changes: 20 additions & 0 deletions src/ast/implementation/yul/expression/yul_typed_name.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { YulExpression } from "./yul_expression";

export class YulTypedName extends YulExpression {
/**
* Name of the identifier
*/
name: string;

/**
* Yul type string, e.g.u256
*/
typeString: string;

constructor(id: number, src: string, name: string, typeString = "", raw?: any) {
super(id, src, raw);

this.name = name;
this.typeString = typeString;
}
}
3 changes: 3 additions & 0 deletions src/ast/implementation/yul/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export * from "./expression";
export * from "./statement";
export * from "./yul_ast_node";
13 changes: 13 additions & 0 deletions src/ast/implementation/yul/statement/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
export * from "./yul_assignment";
export * from "./yul_block";
export * from "./yul_break";
export * from "./yul_case";
export * from "./yul_continue";
export * from "./yul_expression_statement";
export * from "./yul_for_loop";
export * from "./yul_function_definition";
export * from "./yul_if";
export * from "./yul_leave";
export * from "./yul_statement";
export * from "./yul_switch";
export * from "./yul_variable_declaration";
Loading