diff --git a/libraries/analysis-solidity/lib/target/Target.ts b/libraries/analysis-solidity/lib/target/Target.ts index b790227..c3819f7 100644 --- a/libraries/analysis-solidity/lib/target/Target.ts +++ b/libraries/analysis-solidity/lib/target/Target.ts @@ -43,6 +43,7 @@ export type ContractTarget = CoreSubTarget & { export type FunctionTarget = CoreSubTarget & { type: TargetType.FUNCTION; id: string; + contractId: string; name: string; /** @@ -95,3 +96,8 @@ export enum ContractKind { Library = "library", Interface = "interface", } + + +export function isExternal(target: SubTarget) { + return target.type === TargetType.FUNCTION && target.visibility === Visibility.External +} \ No newline at end of file diff --git a/libraries/analysis-solidity/lib/target/TargetVisitor.ts b/libraries/analysis-solidity/lib/target/TargetVisitor.ts index 4184660..9193a73 100644 --- a/libraries/analysis-solidity/lib/target/TargetVisitor.ts +++ b/libraries/analysis-solidity/lib/target/TargetVisitor.ts @@ -34,7 +34,7 @@ import { AbstractSyntaxTreeVisitor } from "../ast/AbstractSyntaxTreeVisitor"; import { NodePath } from "../ast/NodePath"; import { Type, TypeEnum } from "../types/Type"; import { Parameter } from "../types/Parameter"; -import { Visibility, getVisibility } from "../types/Visibility"; +import { getVisibility } from "../types/Visibility"; import { getStateMutability } from "../types/StateMutability"; /** @@ -117,30 +117,7 @@ export class TargetVisitor extends AbstractSyntaxTreeVisitor { return functionParameter; }); - let visibility; - switch (path.node.visibility) { - case "default": { - visibility = Visibility.Public; - break; - } - case "public": { - visibility = Visibility.Public; - break; - } - case "external": { - visibility = Visibility.External; - break; - } - case "internal": { - visibility = Visibility.Internal; - break; - } - case "private": { - visibility = Visibility.Private; - break; - } - } - + const visibility = getVisibility(path.node.visibility) const mutability = getStateMutability(path.node.stateMutability); const overrides = path.node.override @@ -166,6 +143,7 @@ export class TargetVisitor extends AbstractSyntaxTreeVisitor { const contractFunction: FunctionTarget = { type: TargetType.FUNCTION, id: id, + contractId: this._current.id, name: name, isConstructor: path.node.isConstructor, isFallback: !path.node.name, diff --git a/libraries/analysis-solidity/lib/types/Type.ts b/libraries/analysis-solidity/lib/types/Type.ts index fee2f07..4389676 100644 --- a/libraries/analysis-solidity/lib/types/Type.ts +++ b/libraries/analysis-solidity/lib/types/Type.ts @@ -20,6 +20,7 @@ import { Parameter } from "./Parameter"; import { StateMutability } from "./StateMutability"; import { Visibility } from "./Visibility"; + export enum TypeEnum { ADDRESS = "address", BOOL = "bool", @@ -101,6 +102,7 @@ export type StringType = { export type Contract = { type: TypeEnum.CONTRACT; + id: string; }; export type UserDefined = { @@ -143,3 +145,40 @@ export type Type = | FunctionType | Mapping | ArrayType; + + + +export function typeToString(type: Type): string { + switch (type.type) { + case TypeEnum.ADDRESS: + case TypeEnum.STRING: + case TypeEnum.DYNAMIC_SIZE_BYTE_ARRAY: + case TypeEnum.CONTRACT: + + + case TypeEnum.BOOL: { + return type.type // TODO statementMutatbility + } + case TypeEnum.INT: + case TypeEnum.FIXED: { + return `${type.signed ? '' : 'u'}${type.type}${type.bits}` + } + case TypeEnum.FIXED_SIZE_BYTE_ARRAY: { + return `${type.type}${type.bytes}` + } + case TypeEnum.FUNCTION: { + return `${type.type} (${type.parameters.map((t) => typeToString(t.type)).join(', ')}) {${type.visibility}} [${type.stateMutability}] [returns (${type.parameters.map((t) => typeToString(t.type)).join(', ')})]` + } + case TypeEnum.MAPPING: { + return `${type.type} (${typeToString(type.keyType)} => ${typeToString(type.valueType)})` + } + case TypeEnum.USER_DEFINED: { + return `${type.type} (${type.name})` + } + + case TypeEnum.ARRAY: { + return `${type.baseType}[]` + } + + } +} \ No newline at end of file diff --git a/libraries/analysis-solidity/lib/types/Visibility.ts b/libraries/analysis-solidity/lib/types/Visibility.ts index 699b14f..c1822d0 100644 --- a/libraries/analysis-solidity/lib/types/Visibility.ts +++ b/libraries/analysis-solidity/lib/types/Visibility.ts @@ -40,4 +40,4 @@ export function getVisibility(visibility: string): Visibility { // No default } throw new Error("Invalid visibility"); -} +} \ No newline at end of file diff --git a/libraries/search-solidity/index.ts b/libraries/search-solidity/index.ts index 21e884e..36affbb 100644 --- a/libraries/search-solidity/index.ts +++ b/libraries/search-solidity/index.ts @@ -16,9 +16,7 @@ * limitations under the License. */ -export * from "./lib/criterion/RequireObjectiveFunction"; - -export * from "./lib/search/crossover/SolidityTreeCrossover"; +export * from "./lib/search/crossover/TreeCrossover"; export * from "./lib/search/SolidityExecutionResult"; export * from "./lib/search/SoliditySubject"; @@ -32,8 +30,7 @@ export * from "./lib/testcase/sampling/SoliditySampler"; export * from "./lib/testcase/statements/action/ActionStatement"; export * from "./lib/testcase/statements/action/ConstructorCall"; -export * from "./lib/testcase/statements/action/FunctionCall"; -export * from "./lib/testcase/statements/action/ObjectFunctionCall"; +export * from "./lib/testcase/statements/action/ContractFunctionCall"; export * from "./lib/testcase/statements/primitive/AddressStatement"; export * from "./lib/testcase/statements/primitive/BoolStatement"; @@ -42,7 +39,6 @@ export * from "./lib/testcase/statements/primitive/NumericStatement"; export * from "./lib/testcase/statements/primitive/PrimitiveStatement"; export * from "./lib/testcase/statements/primitive/StringStatement"; -export * from "./lib/testcase/statements/RootStatement"; export * from "./lib/testcase/statements/Statement"; export * from "./lib/testcase/SolidityTestCase"; diff --git a/libraries/search-solidity/lib/criterion/RequireObjectiveFunction.ts b/libraries/search-solidity/lib/criterion/RequireObjectiveFunction.ts index bb80c5e..9c4ed39 100644 --- a/libraries/search-solidity/lib/criterion/RequireObjectiveFunction.ts +++ b/libraries/search-solidity/lib/criterion/RequireObjectiveFunction.ts @@ -1,7 +1,7 @@ /* - * Copyright 2020-2022 Delft University of Technology and SynTest contributors + * Copyright 2020-2023 Delft University of Technology and SynTest contributors * - * This file is part of SynTest Solidity. + * This file is part of SynTest Framework - SynTest Solidity. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,132 +16,134 @@ * limitations under the License. */ -import { NodeType, Node } from "@syntest/cfg"; -import { - BranchDistance, - Encoding, - ProbeObjectiveFunction, - SearchSubject, -} from "@syntest/search"; - -export class RequireObjectiveFunction< - T extends Encoding -> extends ProbeObjectiveFunction { - constructor( - subject: SearchSubject, - id: string, - line: number, - type: boolean - ) { - super(subject, id, line, type); - } - - calculateDistance(encoding: T): number { - const executionResult = encoding.getExecutionResult(); - - if (executionResult === undefined) { - return Number.MAX_VALUE; - } - - if (executionResult.coversLine(this._line)) { - const postCondition = executionResult - .getTraces() - .find( - (trace) => trace.type === "probePost" && trace.line === this._line - ); - - const preCondition = executionResult - .getTraces() - .find( - (trace) => trace.type === "probePre" && trace.line === this._line - ); - - if (this.type) { - if (postCondition.hits > 0) return 0; - - if (preCondition.hits > 0) { - return BranchDistance.branchDistanceNumeric( - preCondition.opcode, - preCondition.left, - preCondition.right, - true - ); - } - - return 1; - } else { - if (preCondition.hits == 0) return 1; - - if (preCondition.hits > 0) return 0; - - return BranchDistance.branchDistanceNumeric( - preCondition.opcode, - preCondition.left, - preCondition.right, - false - ); - } - } - - // find the corresponding branch node inside the cfg - const branchNode = this._subject.cfg.nodes.find((n: Node) => { - return ( - n.type === NodeType.Branch && - (n).probe && - n.lines.includes(this._line) - ); - }); - const childEdge = this._subject.cfg.edges.find((edge) => { - return edge.from === branchNode.id && edge.branchType === this._type; - }); - const childNode = this._subject.cfg.nodes.find((node) => { - return node.id === childEdge.to; - }); - - // find the closest covered branch to the objective branch - let closestHitNode; - let approachLevel = Number.MAX_VALUE; - for (const n of this._subject.cfg.nodes) { - const traces = executionResult - .getTraces() - .filter( - (trace) => - n.lines.includes(trace.line) && - (trace.type === "branch" || - trace.type === "probePre" || - trace.type === "probePost" || - trace.type === "function") && - trace.hits > 0 - ); - for (const trace of traces) { - const pathDistance = this._subject.getPath(n.id, childNode.id); - if (approachLevel > pathDistance) { - approachLevel = pathDistance; - closestHitNode = trace; - } - } - } - - // if closer node (branch or probe) is not found, we return the distance to the root branch - if (!closestHitNode) { - return Number.MAX_VALUE; - } - - const branchDistance: number = closestHitNode.type === "function" ? 1 : this.calculateDistance(closestHitNode); - - // add the distances - return approachLevel + branchDistance; - } - - getIdentifier(): string { - return this._id; - } - - getSubject(): SearchSubject { - return this._subject; - } - - get type(): boolean { - return this._type; - } -} +// import { NodeType, Node } from "@syntest/cfg"; +// import { +// BranchDistance, +// Encoding, +// ProbeObjectiveFunction, +// SearchSubject, +// } from "@syntest/search"; + +// export class RequireObjectiveFunction< +// T extends Encoding +// > extends ProbeObjectiveFunction { +// constructor( +// subject: SearchSubject, +// id: string, +// line: number, +// type: boolean +// ) { +// super(subject, id, line, type); +// } + +// calculateDistance(encoding: T): number { +// const executionResult = encoding.getExecutionResult(); + +// if (executionResult === undefined) { +// return Number.MAX_VALUE; +// } + +// if (executionResult.coversLine(this._line)) { +// const postCondition = executionResult +// .getTraces() +// .find( +// (trace) => trace.type === "probePost" && trace.line === this._line +// ); + +// const preCondition = executionResult +// .getTraces() +// .find( +// (trace) => trace.type === "probePre" && trace.line === this._line +// ); + +// if (this.type) { +// if (postCondition.hits > 0) return 0; + +// if (preCondition.hits > 0) { +// return BranchDistance.branchDistanceNumeric( +// preCondition.opcode, +// preCondition.left, +// preCondition.right, +// true +// ); +// } + +// return 1; +// } else { +// if (preCondition.hits == 0) return 1; + +// if (preCondition.hits > 0) return 0; + +// return BranchDistance.branchDistanceNumeric( +// preCondition.opcode, +// preCondition.left, +// preCondition.right, +// false +// ); +// } +// } + +// // find the corresponding branch node inside the cfg +// const branchNode = this._subject.cfg.nodes.find((n: Node) => { +// return ( +// n.type === NodeType.Branch && +// (n).probe && +// n.lines.includes(this._line) +// ); +// }); +// const childEdge = this._subject.cfg.edges.find((edge) => { +// return edge.from === branchNode.id && edge.branchType === this._type; +// }); +// const childNode = this._subject.cfg.nodes.find((node) => { +// return node.id === childEdge.to; +// }); + +// // find the closest covered branch to the objective branch +// let closestHitNode; +// let approachLevel = Number.MAX_VALUE; +// for (const n of this._subject.cfg.nodes) { +// const traces = executionResult +// .getTraces() +// .filter( +// (trace) => +// n.lines.includes(trace.line) && +// (trace.type === "branch" || +// trace.type === "probePre" || +// trace.type === "probePost" || +// trace.type === "function") && +// trace.hits > 0 +// ); +// for (const trace of traces) { +// const pathDistance = this._subject.getPath(n.id, childNode.id); +// if (approachLevel > pathDistance) { +// approachLevel = pathDistance; +// closestHitNode = trace; +// } +// } +// } + +// // if closer node (branch or probe) is not found, we return the distance to the root branch +// if (!closestHitNode) { +// return Number.MAX_VALUE; +// } + +// const branchDistance: number = closestHitNode.type === "function" ? 1 : this.calculateDistance(closestHitNode); + +// // add the distances +// return approachLevel + branchDistance; +// } + +// getIdentifier(): string { +// return this._id; +// } + +// getSubject(): SearchSubject { +// return this._subject; +// } + +// get type(): boolean { +// return this._type; +// } +// } +console.log() +//TODO diff --git a/libraries/search-solidity/lib/search/crossover/SolidityTreeCrossover.ts b/libraries/search-solidity/lib/search/crossover/SolidityTreeCrossover.ts deleted file mode 100644 index 2868859..0000000 --- a/libraries/search-solidity/lib/search/crossover/SolidityTreeCrossover.ts +++ /dev/null @@ -1,166 +0,0 @@ -/* - * Copyright 2020-2022 Delft University of Technology and SynTest contributors - * - * This file is part of SynTest Solidity. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { Crossover } from "@syntest/search"; - -import { SolidityTestCase } from "../../testcase/SolidityTestCase"; -import { ConstructorCall } from "../../testcase/statements/action/ConstructorCall"; -import { NumericStatement } from "../../testcase/statements/primitive/NumericStatement"; -import { Statement } from "../../testcase/statements/Statement"; -import { prng } from "@syntest/prng"; - -interface QueueEntry { - parent: Statement; - childIndex: number; - child: Statement; -} - -/** - * Creates 2 children which are each other's complement with respect to their parents. - * i.e. given parents 000000 and 111111 a possible pair of children would be 001111 and 110000. - * However, it is not as simple because the actual mutation works with trees. - * - * @param parentA the first parent individual - * @param parentB the second parent individual - * - * @return a tuple of 2 children - * - * @author Annibale Panichella - * @author Dimitri Stallenberg - */ -export class SolidityTreeCrossover extends Crossover { - public crossOver(parents: SolidityTestCase[]): SolidityTestCase[] { - if (parents.length !== 2) { - throw new Error("Expected 2 parents got: " + parents.length); - } - const rootA = parents[0].copy().root; - const rootB = parents[1].copy().root; - - const queueA: QueueEntry[] = []; - - for ( - let index = 0; - index < (rootA as ConstructorCall).getMethodCalls().length; - index++ - ) { - queueA.push({ - parent: rootA, - childIndex: index, - child: (rootA as ConstructorCall).getMethodCalls()[index], - }); - } - - const crossoverOptions = []; - - while (queueA.length > 0) { - const pair = queueA.shift(); - - if (pair.child.hasChildren()) { - // eslint-disable-next-line unicorn/no-array-for-each - pair.child.getChildren().forEach((child: Statement, index: number) => { - queueA.push({ - parent: pair.child, - childIndex: index, - child: child, - }); - }); - } - - if (prng.nextBoolean(this.crossoverStatementProbability)) { - // crossover - const donorSubtrees = this.findSimilarSubtree(pair.child, rootB); - - for (const donorTree of donorSubtrees) { - crossoverOptions.push({ - p1: pair, - p2: donorTree, - }); - } - } - } - - if (crossoverOptions.length > 0) { - const crossoverChoice = prng.pickOne(crossoverOptions); - const pair = crossoverChoice.p1; - const donorTree = crossoverChoice.p2; - - pair.parent.setChild(pair.childIndex, donorTree.child.copy()); - donorTree.parent.setChild(donorTree.childIndex, pair.child.copy()); - } - - // TODO i think those are not necceasry - // rootA.args = [...parentA.root.args]; - // rootB.args = [...parentB.root.args]; - return [ - new SolidityTestCase(rootA as ConstructorCall), - new SolidityTestCase(rootB as ConstructorCall), - ]; - } - - /** - * Finds a subtree in the given tree which matches the wanted gene. - * - * @param wanted the gene to match the subtree with - * @param tree the tree to search in - * - * @author Dimitri Stallenberg - */ - protected findSimilarSubtree(wanted: Statement, tree: Statement) { - const queue: QueueEntry[] = []; - const similar = []; - - for (let index = 0; index < tree.getChildren().length; index++) { - queue.push({ - parent: tree, - childIndex: index, - child: tree.getChildren()[index], - }); - } - - while (queue.length > 0) { - const pair = queue.shift(); - - if (pair.child.hasChildren()) { - // eslint-disable-next-line unicorn/no-array-for-each - pair.child.getChildren().forEach((child: Statement, index: number) => { - queue.push({ - parent: pair.child, - childIndex: index, - child: child, - }); - }); - } - - if (wanted.types === pair.child.types) { - if (wanted instanceof NumericStatement) { - if ( - wanted.upper_bound == - (pair.child as NumericStatement).upper_bound && - wanted.lower_bound == (pair.child as NumericStatement).lower_bound - ) { - similar.push(pair); - } - } else { - similar.push(pair); - } - } - } - - return similar; - } -} diff --git a/libraries/search-solidity/lib/search/crossover/TreeCrossover.ts b/libraries/search-solidity/lib/search/crossover/TreeCrossover.ts new file mode 100644 index 0000000..2af97f2 --- /dev/null +++ b/libraries/search-solidity/lib/search/crossover/TreeCrossover.ts @@ -0,0 +1,155 @@ +/* + * Copyright 2020-2022 Delft University of Technology and SynTest contributors + * + * This file is part of SynTest Solidity. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Crossover } from "@syntest/search"; + +import { SolidityTestCase } from "../../testcase/SolidityTestCase"; +import { Statement } from "../../testcase/statements/Statement"; +import { prng } from "@syntest/prng"; +import { ActionStatement } from "../../testcase/statements/action/ActionStatement"; +import { typeToString } from "@syntest/analysis-solidity"; + +type SwapStatement = { + parent: Statement; + childIndex: number; + child: Statement; +}; + +type MatchingPair = { + parentA: SwapStatement; + parentB: SwapStatement; +}; + +/** + * Creates 2 children which are each other's complement with respect to their parents. + * i.e. given parents 000000 and 111111 a possible pair of children would be 001111 and 110000. + * However, it is not as simple because the actual mutation works with trees. + * + * @param parentA the first parent individual + * @param parentB the second parent individual + * + * @return a tuple of 2 children + */ +export class TreeCrossover extends Crossover { + public crossOver(parents: SolidityTestCase[]): SolidityTestCase[] { + if (parents.length !== 2) { + throw new Error("Expected exactly 2 parents, got: " + parents.length); + } + + const rootA: ActionStatement[] = (parents[0].copy()) + .roots; + const rootB: ActionStatement[] = (parents[1].copy()) + .roots; + + const swapStatementsA = this.convertToSwapStatements(rootA); + const swapStatementsB = this.convertToSwapStatements(rootB); + + const crossoverOptions: MatchingPair[] = []; + + for (const swapA of swapStatementsA) { + for (const swapB of swapStatementsB) { + if (!swapA.child.type || !swapB.child.type) { + throw new Error("All statements require a type!"); + } + + if (typeToString(swapA.child.type.type) === typeToString(swapB.child.type.type)) { + crossoverOptions.push({ + parentA: swapA, + parentB: swapB, + }); + } + } + } + + if (crossoverOptions.length > 0) { + // TODO this ignores _crossoverStatementProbability and always picks one + + const matchingPair = prng.pickOne(crossoverOptions); + const parentA = matchingPair.parentA; + const parentB = matchingPair.parentB; + + if (parentA.parent !== undefined && parentB.parent !== undefined) { + parentA.parent.setChild(parentA.childIndex, parentB.child.copy()); + parentB.parent.setChild(parentB.childIndex, parentA.child.copy()); + } else if (parentB.parent !== undefined) { + if (!(parentB.child instanceof ActionStatement)) { + throw new TypeError( + "expected parentB child to be an actionstatement" + ); + } + rootA[parentA.childIndex] = parentB.child.copy(); + parentB.parent.setChild(parentB.childIndex, parentA.child.copy()); + } else if (parentA.parent === undefined) { + if (!(parentA.child instanceof ActionStatement)) { + throw new TypeError( + "expected parentA child to be an actionstatement" + ); + } + if (!(parentB.child instanceof ActionStatement)) { + throw new TypeError( + "expected parentB child to be an actionstatement" + ); + } + rootA[parentA.childIndex] = parentB.child.copy(); + rootB[parentB.childIndex] = parentA.child.copy(); + } else { + if (!(parentA.child instanceof ActionStatement)) { + throw new TypeError( + "expected parentA child to be an actionstatement" + ); + } + parentA.parent.setChild(parentA.childIndex, parentB.child.copy()); + rootB[parentB.childIndex] = parentA.child.copy(); + } + } + + return [new SolidityTestCase(rootA), new SolidityTestCase(rootB)]; + } + + protected convertToSwapStatements(roots: ActionStatement[]): SwapStatement[] { + const swapStatements: SwapStatement[] = []; + + for (const [index, root] of roots.entries()) { + swapStatements.push({ + parent: undefined, + childIndex: index, + child: root, + }); + } + + const queue: Statement[] = [...roots]; + + while (queue.length > 0) { + const statement = queue.shift(); + + if (statement.hasChildren()) { + for (let index = 0; index < statement.getChildren().length; index++) { + const child = statement.getChildren()[index]; + swapStatements.push({ + parent: statement, + childIndex: index, + child: child, + }); + queue.push(child); + } + } + } + + return swapStatements; + } +} \ No newline at end of file diff --git a/libraries/search-solidity/lib/testbuilding/ContextBuilder.ts b/libraries/search-solidity/lib/testbuilding/ContextBuilder.ts new file mode 100644 index 0000000..d0e7796 --- /dev/null +++ b/libraries/search-solidity/lib/testbuilding/ContextBuilder.ts @@ -0,0 +1,190 @@ +/* + * Copyright 2020-2023 Delft University of Technology and SynTest contributors + * + * This file is part of SynTest Framework - SynTest Solidity. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Parameter } from "@syntest/analysis-solidity"; +import { Decoding } from "./Decoding"; +import { ConstructorCall } from "../testcase/statements/action/ConstructorCall"; + +type Import = { + name: string; + renamed: boolean; + renamedTo?: string; +}; + +// TODO we can also use this to generate unique identifier for the statements itself +// TODO gather assertions here too per test case +export class ContextBuilder { + private contractDependencies: Map; + + // name -> import + private imports: Map; + + private logsPresent: boolean; + private assertionsPresent: boolean; + + // old var -> new var + private variableMap: Map; + // var -> count + private variableCount: Map; + + + private statementVariableNameMap: Map + private variableNameCount: Map + + constructor(contractDependencies: Map) { + this.contractDependencies = contractDependencies + + this.imports = new Map(); + + this.logsPresent = false; + this.assertionsPresent = false; + + this.variableMap = new Map(); + this.variableCount = new Map(); + + this.statementVariableNameMap = new Map() + this.variableNameCount = new Map() + } + + getOrCreateVariableName(parameter: Parameter): string { + // to camelcase + + if (this.statementVariableNameMap.has(parameter)) { + return this.statementVariableNameMap.get(parameter) + } + + let variableName = '' + + if (this.variableNameCount.has(variableName)) { + const count = this.variableNameCount.get(variableName) + this.variableNameCount.set(variableName, count + 1) + variableName += count + } else { + this.variableNameCount.set(variableName, 1) + } + + this.statementVariableNameMap.set(parameter, variableName) + return variableName + } + + addDecoding(decoding: Decoding) { + // This function assumes the decodings to come in order + + if (decoding.reference instanceof ConstructorCall) { + const import_ = this._addImport(decoding.reference.type.name); + const newName = import_.renamed ? import_.renamedTo : import_.name; + decoding.decoded = decoding.decoded.replaceAll(import_.name, newName); + } + + const variableName = this.getOrCreateVariableName(decoding.reference.type); + if (this.variableMap.has(variableName)) { + this.variableCount.set( + variableName, + this.variableCount.get(variableName) + 1 + ); + } else { + this.variableCount.set(variableName, 0); + } + + this.variableMap.set( + variableName, + variableName + this.variableCount.get(variableName) + ); + + for (const [oldVariable, newVariable] of this.variableMap.entries()) { + decoding.decoded = decoding.decoded.replaceAll(oldVariable, newVariable); + } + } + + addLogs() { + this.logsPresent = true; + } + + addAssertions() { + this.assertionsPresent = true; + } + + private _addImport(name: string): Import { + const import_: Import = { + name: name, + renamed: false, + }; + + if (this.imports.has(name)) { + return this.imports.get(name); + } + + this.imports.set(name, import_) + return import_; + } + + // TODO we could gather all the imports of a certain path together into one import + private _getImportString(name: string): string { + return `const ${name} = artifacts.require("${name}")` + } + + private getLinkingStrings(contract: string, dependency: string, count: number): string[] { + return [ + `const lib${count} = await ${dependency}.new();`, + `await ${contract}.link('${dependency}', lib${count}.address);` + ] + } + + getImports(): [string[], string[]] { + const imports: string[] = []; + const linkings: string[] = [] + + let count = 0 + for (const import_ of this.imports.values()) { + // TODO remove unused imports + const dependencies = this.contractDependencies.get(import_.name) + + for (const dependency of dependencies) { + linkings.push(...this.getLinkingStrings(import_.name, dependency, count)) + + count++; + } + + imports.push(this._getImportString(import_.name)); + } + + if (this.assertionsPresent) { + imports.push( + `const chai = require('chai')`, + `const chaiAsPromised = require('chai-as-promised')`, + `const expect = chai.expect;`, + `chai.use(chaiAsPromised);` + ); + } + + if (this.logsPresent) { + imports.push(`import * as fs from 'fs'`); + } + // TODO other post processing? + return [( + imports + // remove duplicates + // there should not be any in theory but lets do it anyway + .filter((value, index, self) => self.indexOf(value) === index) + // sort + .sort() + ), ( + linkings + )]; + } +} diff --git a/libraries/search-solidity/lib/testbuilding/Decoding.ts b/libraries/search-solidity/lib/testbuilding/Decoding.ts new file mode 100644 index 0000000..525bdc9 --- /dev/null +++ b/libraries/search-solidity/lib/testbuilding/Decoding.ts @@ -0,0 +1,26 @@ +/* + * Copyright 2020-2023 Delft University of Technology and SynTest contributors + * + * This file is part of SynTest Framework - SynTest Solidity. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Statement } from "../testcase/statements/Statement"; + +export type Decoding = { + decoded: string; + + reference: Statement; + } + \ No newline at end of file diff --git a/libraries/search-solidity/lib/testbuilding/SolidityDecoder.ts b/libraries/search-solidity/lib/testbuilding/SolidityDecoder.ts index 1e2cb75..e096247 100644 --- a/libraries/search-solidity/lib/testbuilding/SolidityDecoder.ts +++ b/libraries/search-solidity/lib/testbuilding/SolidityDecoder.ts @@ -19,237 +19,21 @@ import { Decoder } from "@syntest/search"; import * as path from "node:path"; -import * as web3_utils from "web3-utils"; -import { ArrayStatement } from "../testcase/statements/complex/ArrayStatement"; -import { AddressStatement } from "../testcase/statements/primitive/AddressStatement"; -import { ConstructorCall } from "../testcase/statements/action/ConstructorCall"; -import { StringStatement } from "../testcase/statements/primitive/StringStatement"; -import { ObjectFunctionCall } from "../testcase/statements/action/ObjectFunctionCall"; import { SolidityTestCase } from "../testcase/SolidityTestCase"; -import { Statement } from "../testcase/statements/Statement"; -import { PrimitiveStatement } from "../testcase/statements/primitive/PrimitiveStatement"; +import { ContextBuilder } from "./ContextBuilder"; +import { ActionStatement } from "../testcase/statements/action/ActionStatement"; +import { Decoding } from "./Decoding"; /** * Solidity Decoder */ export class SolidityDecoder implements Decoder { - private targetRootDirectory: string; private tempLogDirectory: string; + private contractDependencies: Map; - constructor(targetRootDirectory: string, temporaryLogDirectory: string) { - this.targetRootDirectory = targetRootDirectory; + constructor(temporaryLogDirectory: string, contractDependencies: Map) { this.tempLogDirectory = temporaryLogDirectory; - } - - decodeConstructor(statement: Statement): string { - if (!(statement instanceof ConstructorCall)) - throw new Error(`${statement} is not a constructor`); - - let string = ""; - - const arguments_ = (statement as ConstructorCall).arguments_; - for (const argument of arguments_) { - if (argument instanceof PrimitiveStatement) { - string = string + this.decodeStatement(argument) + "\n\t\t"; - } - } - const formattedArguments = arguments_ - .map((a: PrimitiveStatement) => a.varName) - .join(", "); - - const sender = (statement as ConstructorCall).getSender().getValue(); - const senderString = - formattedArguments == "" ? `{from: ${sender}}` : `, {from: ${sender}}`; - return ( - string + - `const ${statement.varNames[0]} = await ${ - (statement as ConstructorCall).constructorName - }.new(${formattedArguments}${senderString});` - ); - } - - decodeErroringConstructorCall(statement: Statement): string { - if (!(statement instanceof ConstructorCall)) - throw new Error(`${statement} is not a constructor`); - - let string = ""; - - const arguments_ = (statement as ConstructorCall).arguments_; - for (const argument of arguments_) { - if (argument instanceof PrimitiveStatement) { - string = string + this.decodeStatement(argument) + "\n\t\t"; - } - } - const formattedArguments = arguments_ - .map((a: PrimitiveStatement) => a.varName) - .join(", "); - - const sender = (statement as ConstructorCall).getSender().getValue(); - const senderString = - formattedArguments == "" ? `{from: ${sender}}` : `, {from: ${sender}}`; - - return ( - string + - `await expect(${ - (statement as ConstructorCall).constructorName - }.new(${formattedArguments}${senderString}).to.be.rejectedWith(Error);` - ); - } - - decodeStatement(statement: Statement): string { - if (!(statement instanceof PrimitiveStatement)) { - throw new TypeError(`${statement} is not a primitive statement`); - } - - const primitive: PrimitiveStatement = - statement as PrimitiveStatement; - // TODO what happened to float support? - if ( - statement.type.type.startsWith("int") || - statement.type.type.startsWith("uint") - ) { - const value = primitive.value.toFixed(0); - return `const ${statement.varName} = BigInt("${value}")`; - } else if (statement instanceof StringStatement) { - return `const ${statement.varName} = "${primitive.value}"`; - } else if (statement instanceof AddressStatement) { - return (statement as AddressStatement).toCode(); - } else if (statement instanceof ArrayStatement) { - const bytes = web3_utils.bytesToHex((statement as ArrayStatement).value); - return `const ${statement.varName} = "${bytes}"`; - } else { - return `const ${statement.varName} = ${primitive.value}`; - } - } - - decodeFunctionCall(statement: Statement, objectName: string): string { - if (statement instanceof ObjectFunctionCall) { - const arguments_ = (statement as ObjectFunctionCall).getChildren(); - // TODO the difficulty now is to select the correct var from the statements.... - // TODO now assuming its always the first one - const formattedArguments = arguments_ - .map((a: Statement) => a.varNames[0]) - .join(", "); - - // TODO not sure how the multi args are returned to javascript (since javascript does not support this syntax - // TODO assuming it gets wrapped into an array - - const sender = (statement as ObjectFunctionCall).getSender().getValue(); - const senderString = - formattedArguments == "" ? `{from: ${sender}}` : `, {from: ${sender}}`; - - if ( - statement.types.length > 0 && - !( - statement.types.length === 1 && - ["void", "none"].includes(statement.types[0].type) - ) - ) { - let variableNames = statement.varNames[0]; - if (statement.types.length > 1) { - variableNames = `[${statement.varNames.join(", ")}]`; - } - return `const ${variableNames} = await ${objectName}.${ - (statement as ObjectFunctionCall).functionName - }.call(${formattedArguments}${senderString});`; - } - return `await ${objectName}.${ - (statement as ObjectFunctionCall).functionName - }.call(${formattedArguments}${senderString});`; - } else { - throw new TypeError(`${statement} is not a function call`); - } - } - - decodeErroringFunctionCall(statement: Statement, objectName: string): string { - if (statement instanceof ObjectFunctionCall) { - const arguments_ = (statement as ObjectFunctionCall).getChildren(); - // TODO the difficulty now is to select the correct var from the statements.... - // TODO now assuming its always the first one - const formattedArguments = arguments_ - .map((a: Statement) => a.varNames[0]) - .join(", "); - - const sender = (statement as ObjectFunctionCall).getSender().getValue(); - const senderString = - formattedArguments == "" ? `{from: ${sender}}` : `, {from: ${sender}}`; - - return `await expect(${objectName}.${ - (statement as ObjectFunctionCall).functionName - }.call(${formattedArguments}${senderString})).to.be.rejectedWith(Error);`; - } else { - throw new TypeError(`${statement} is not a function call`); - } - } - - getImport(constructorName: string): string { - if (!this.imports.has(constructorName)) { - throw new Error( - `Cannot find the import, constructor: ${constructorName} belongs to` - ); - } - - return `const ${constructorName} = artifacts.require("${this.imports.get( - constructorName - )}");`; - } - - convertToStatementStack(ind: SolidityTestCase): Statement[] { - const stack: Statement[] = []; - const queue: Statement[] = [ind.root]; - while (queue.length > 0) { - const current: Statement = queue.splice(0, 1)[0]; - - if (current instanceof ConstructorCall) { - for (const call of current.getMethodCalls()) { - queue.push(call); - } - } else { - stack.push(current); - - for (const child of current.getChildren()) { - queue.push(child); - } - } - } - return stack; - } - - gatherImports(importableGenes: ConstructorCall[]): [string[], string[]] { - const imports: string[] = []; - const linkings: string[] = []; - for (const gene of importableGenes) { - const contract = gene.constructorName; - - const importString: string = this.getImport(contract); - - if (imports.includes(importString) || importString.length === 0) { - continue; - } - - imports.push(importString); - - let count = 0; - for (const dependency of this.contractDependencies.get(contract)) { - const importString: string = this.getImport(dependency.targetName); - - // Create link - linkings.push( - `\t\tconst lib${count} = await ${dependency.targetName}.new();`, - `\t\tawait ${contract}.link('${dependency.targetName}', lib${count}.address);` - ); - - if (imports.includes(importString) || importString.length === 0) { - continue; - } - - imports.push(importString); - - count += 1; - } - } - - return [imports, linkings]; + this.contractDependencies = contractDependencies } generateAssertions(ind: SolidityTestCase): string[] { @@ -288,174 +72,155 @@ export class SolidityDecoder implements Decoder { } decode( - testCase: SolidityTestCase | SolidityTestCase[], + testCases: SolidityTestCase | SolidityTestCase[], targetName: string, - addLogs = false, - sourceDirectory = "../instrumented" + addLogs = false ): string { - if (testCase instanceof SolidityTestCase) { - testCase = [testCase]; + if (testCases instanceof SolidityTestCase) { + testCases = [testCases]; } + const context = new ContextBuilder(this.contractDependencies) + const tests: string[] = []; - const imports: string[] = []; + for (const testCase of testCases) { + const roots: ActionStatement[] = testCase.roots; - for (const ind of testCase) { - // The stopAfter variable makes sure that when one of the function calls has thrown an exception the test case ends there. - let stopAfter = -1; - if (ind.assertions.size > 0 && ind.assertions.has("error")) { - stopAfter = ind.assertions.size; - } + let decodings: Decoding[] = roots.flatMap((root) => + root.decode(context, false) + ); + if (decodings.length === 0) { + throw new Error("No statements in test case"); + } + const testString = []; - const stack: Statement[] = this.convertToStatementStack(ind); if (addLogs) { - imports.push(`const fs = require('fs');\n\n`); testString.push( `\t\tawait fs.mkdirSync('${path.join( - CONFIG.tempLogDirectory, - ind.id - )}', { recursive: true })\n`, - "try {" + this.tempLogDirectory, + testCase.id + )}', { recursive: true })\n + \t\tlet count = 0; + \t\ttry {\n` ); } - const importableGenes: ConstructorCall[] = []; - - const constructor = ind.root; - stack.push(constructor); - - let primitiveStatements: string[] = []; - const functionCalls: string[] = []; - const assertions: string[] = []; - - let count = 1; - while (stack.length > 0) { - const gene: Statement = stack.pop(); - - if (gene instanceof ConstructorCall) { - if ( - count === stopAfter && // assertions.push(`\t\t${this.decodeErroringConstructorCall(gene)}`); - CONFIG.testMinimization - ) - break; - testString.push(`\t\t${this.decodeConstructor(gene)}`); - importableGenes.push(gene); - count += 1; - } else if (gene instanceof PrimitiveStatement) { - primitiveStatements.push(`\t\t${this.decodeStatement(gene)}`); - } else if (gene instanceof ObjectFunctionCall) { - if (count === stopAfter) { - assertions.push( - `\t\t${this.decodeErroringFunctionCall( - gene, - constructor.varNames[0] - )}` - ); - if (CONFIG.testMinimization) break; - } - functionCalls.push( - `\t\t${this.decodeFunctionCall(gene, constructor.varNames[0])}` - ); - count += 1; - } else { - throw new TypeError(`The type of gene ${gene} is not recognized`); + if (testCase.assertions.size > 0 && testCase.assertions.has("error")) { + const index = Number.parseInt(testCase.assertions.get("error")); + + // TODO does not work + // the .to.throw stuff does not work somehow + // const decoded = statements[index].reference instanceof MethodCall + // ? (statements[index].reference).decodeWithObject(testCase.id, { addLogs, exception: true }, statements[index].objectVariable) + // : statements[index].reference.decode(testCase.id, { addLogs, exception: true }) + // statements[index] = decoded.find((x) => x.reference === statements[index].reference) + + // delete statements after + decodings = decodings.slice(0, index + 1); + } + + if (decodings.length === 0) { + throw new Error("No statements in test case after error reduction"); + } + + for (const [index, value] of decodings.entries()) { + context.addDecoding(value); + const asString = "\t\t" + value.decoded.replace("\n", "\n\t\t"); + if (testString.includes(asString)) { + // skip repeated statements + continue; } if (addLogs) { - if (gene instanceof ObjectFunctionCall) { - for (const variableName of gene.varNames) { - functionCalls.push( - `\t\tawait fs.writeFileSync('${path.join( - this.tempLogDirectory, - ind.id, - variableName - )}', '' + ${variableName})` - ); - } - } else if (gene instanceof ConstructorCall) { - for (const variableName of gene.varNames) { - testString.push( - `\t\tawait fs.writeFileSync('${path.join( - this.tempLogDirectory, - ind.id, - variableName - )}', '' + ${variableName})` - ); - } - } + // add log per statement + testString.push("\t\t" + `count = ${index};`); } - } - - // filter non-required statements - primitiveStatements = primitiveStatements.filter((s) => { - const variableName = s.split(" ")[1]; - return ( - functionCalls.find((f) => f.includes(variableName)) || - assertions.find((f) => f.includes(variableName)) - ); - }); - testString.push(...primitiveStatements, ...functionCalls); + testString.push(asString); + } if (addLogs) { - testString.push(`} catch (e) {`, + testString.push( + `} catch (e) {`, `await fs.writeFileSync('${path.join( this.tempLogDirectory, - ind.id, + testCase.id, "error" - )}', '' + e.stack)`, + )}', '' + count)`, // TODO we could add the error here and assert that that is the error message we expect "}" ); } - const [importsOfTest, linkings] = this.gatherImports(importableGenes); - imports.push(...importsOfTest); + if (addLogs) { + context.addLogs(); + } - if (ind.assertions.size > 0) { - imports.push( - `const chai = require('chai');`, - `const expect = chai.expect;`, - `chai.use(require('chai-as-promised'));` - ); + if (testCase.assertions.size > 0) { + context.addAssertions(); } - assertions.unshift(...this.generateAssertions(ind)); + const assertions: string[] = this.generateAssertions(testCase); - const body = []; - if (linkings.length > 0) { - body.push(`${linkings.join("\n")}`); + if (assertions.length > 0) { + assertions.splice(0, 0, "\n\t\t// Assertions"); } + + const body = []; + if (testString.length > 0) { - body.push(`${testString.join("\n")}`); + let errorStatement: string; + if (testCase.assertions.size > 0 && testCase.assertions.has("error")) { + errorStatement = testString.pop(); + } + + body.push(`${testString.join("\n")}`, `${assertions.join("\n")}`); + + if (errorStatement) { + body.push( + `\t\ttry {\n\t${errorStatement}\n\t\t} catch (e) {\n\t\t\texpect(e).to.be.an('error')\n\t\t}` + ); + } } - if (assertions.length > 0) { - body.push(`${assertions.join("\n")}`); + + const metaCommentBlock = []; + + for (const metaComment of testCase.metaComments) { + metaCommentBlock.push(`\t\t// ${metaComment}`); + } + + if (metaCommentBlock.length > 0) { + metaCommentBlock.splice(0, 0, "\n\t\t// Meta information"); } // TODO instead of using the targetName use the function call or a better description of the test tests.push( - `\tit('test for ${targetName}', async () => {\n` + - `${body.join("\n\n")}` + - `\n\t});` + `${metaCommentBlock.join("\n")}\n` + + `\n\t\t// Test\n` + + `${body.join("\n\n")}` ); + } - let test = - `contract('${targetName}', (accounts) => {\n` + - tests.join("\n\n") + - `\n})`; - - // Add the imports - test = - imports - .filter((value, index, self) => self.indexOf(value) === index) - .join("\n") + - `\n\n` + - test; - - return test; + const [imports, linkings] = context.getImports(); + const importsString = imports.join("\n") + `\n\n`; + const linkingsString = '\t\t' + linkings.join("\n\t\t") + '\n\n' + + return `// Imports\n` + + importsString + + `contract('${targetName}', function(accounts) {\n\t` + + tests + .map( + (test) => + `\tit('test for ${targetName}', async () => {\n` + + '\t\t// Linkings\n' + + linkingsString + + test + + `\n\t});` + ) + .join("\n\n") + + `\n})` } } diff --git a/libraries/search-solidity/lib/testbuilding/SoliditySuiteBuilder.ts b/libraries/search-solidity/lib/testbuilding/SoliditySuiteBuilder.ts index 92f2281..a42eb0c 100644 --- a/libraries/search-solidity/lib/testbuilding/SoliditySuiteBuilder.ts +++ b/libraries/search-solidity/lib/testbuilding/SoliditySuiteBuilder.ts @@ -94,8 +94,7 @@ export class SoliditySuiteBuilder { const decodedTest = this.decoder.decode( archive.get(key), `${key}`, - addLogs, - sourceDirectory + addLogs ); const testPath = this.storageManager.store( [testDirectory], @@ -111,8 +110,7 @@ export class SoliditySuiteBuilder { const decodedTest = this.decoder.decode( testCase, "", - addLogs, - sourceDirectory + addLogs ); const testPath = this.storageManager.store( [testDirectory], diff --git a/libraries/search-solidity/lib/testcase/SolidityTestCase.ts b/libraries/search-solidity/lib/testcase/SolidityTestCase.ts index 5dce686..d96a8a0 100644 --- a/libraries/search-solidity/lib/testcase/SolidityTestCase.ts +++ b/libraries/search-solidity/lib/testcase/SolidityTestCase.ts @@ -17,9 +17,11 @@ */ import { Encoding, Decoder } from "@syntest/search"; -import { ConstructorCall } from "./statements/action/ConstructorCall"; import { SoliditySampler } from "./sampling/SoliditySampler"; import { Logger, getLogger } from "@syntest/logging"; +import { ActionStatement } from "./statements/action/ActionStatement"; +import { prng } from "@syntest/prng"; +import { StatementPool } from "./StatementPool"; /** * SolidityTestCase class * @@ -28,24 +30,67 @@ import { Logger, getLogger } from "@syntest/logging"; */ export class SolidityTestCase extends Encoding { protected static LOGGER: Logger; - private _root: ConstructorCall; + + private _roots: ActionStatement[]; + + private _statementPool: StatementPool; /** * Constructor. * * @param root The root of the tree chromosome of the test case */ - constructor(root: ConstructorCall) { + constructor(roots: ActionStatement[]) { super(); SolidityTestCase.LOGGER = getLogger("SolidityTestCase"); - this._root = root; + + this._roots = roots.map((value) => value.copy()); + + if (roots.length === 0) { + throw new Error("Requires atleast one root action statement"); + } + + this._statementPool = new StatementPool(roots); + } mutate(sampler: SoliditySampler) { SolidityTestCase.LOGGER.debug(`Mutating test case: ${this._id}`); - return new SolidityTestCase( - (this._root as ConstructorCall).mutate(sampler, 0) - ); + + sampler.statementPool = this._statementPool; + const roots = this._roots.map((action) => action.copy()); + + const choice = prng.nextDouble(); + + if (roots.length > 1) { + if (choice < 0.33) { + // 33% chance to add a root on this position + const index = prng.nextInt(0, roots.length); + roots.splice(index, 0, sampler.sampleRoot()); + } else if (choice < 0.66) { + // 33% chance to delete the root + const index = prng.nextInt(0, roots.length - 1); + roots.splice(index, 1); + } else { + // 33% chance to just mutate the root + const index = prng.nextInt(0, roots.length - 1); + roots.splice(index, 1, roots[index].mutate(sampler, 1)); + } + } else { + if (choice < 0.5) { + // 50% chance to add a root on this position + const index = prng.nextInt(0, roots.length); + roots.splice(index, 0, sampler.sampleRoot()); + } else { + // 50% chance to just mutate the root + const index = prng.nextInt(0, roots.length - 1); + roots.splice(index, 1, roots[index].mutate(sampler, 1)); + } + } + + sampler.statementPool = undefined; + + return new SolidityTestCase(roots); } hashCode(decoder: Decoder): number { @@ -60,14 +105,14 @@ export class SolidityTestCase extends Encoding { } copy(): SolidityTestCase { - return new SolidityTestCase(this.root.copy()); + return new SolidityTestCase(this._roots.map((root) => root.copy())) } getLength(): number { - return (this.root as ConstructorCall).getMethodCalls().length; + return this._roots.length; } - get root(): ConstructorCall { - return this._root; + get roots(): ActionStatement[] { + return this._roots.map((value) => value.copy()); } } diff --git a/libraries/search-solidity/lib/testcase/StatementPool.ts b/libraries/search-solidity/lib/testcase/StatementPool.ts new file mode 100644 index 0000000..6aac003 --- /dev/null +++ b/libraries/search-solidity/lib/testcase/StatementPool.ts @@ -0,0 +1,74 @@ +/* + * Copyright 2020-2023 Delft University of Technology and SynTest contributors + * + * This file is part of SynTest Framework - SynTest JavaScript. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { ActionStatement } from "./statements/action/ActionStatement"; +import { Statement } from "./statements/Statement"; +import { prng } from "@syntest/prng"; +import { Parameter } from "@syntest/analysis-solidity"; +import { typeToString } from "@syntest/analysis-solidity"; +import { Type } from "@syntest/analysis-solidity"; +import { ContractFunctionCall } from "./statements/action/ContractFunctionCall"; + + +export class StatementPool { + // type -> statement array + private pool: Map; + + + constructor(roots: ActionStatement[]) { + this.pool = new Map(); + this._fillGenePool(roots); + } + + public getRandomStatement(type: Type): Statement { + const statements = this.pool.get(typeToString(type)); + + if (!statements || statements.length === 0) { + return undefined; + } + + return prng.pickOne(statements); + } + + private _fillGenePool(roots: ActionStatement[]) { + for (const action of roots) { + const queue: Statement[] = [action]; + + while (queue.length > 0) { + const statement = queue.pop(); + + if (statement.hasChildren()) { + queue.push(...statement.getChildren()); + } + + let types: Parameter[] = [statement.type]; + + if (statement instanceof ContractFunctionCall) { + types = statement.type.type.returns + } + + for (const type of types) { + const typeAsString = typeToString(type.type) + if (!this.pool.has(typeAsString)) { + this.pool.set(typeAsString, []); + } + this.pool.get(typeAsString).push(statement); + } + } + } + } +} diff --git a/libraries/search-solidity/lib/testcase/sampling/SolidityRandomSampler.ts b/libraries/search-solidity/lib/testcase/sampling/SolidityRandomSampler.ts index eea5835..6e629d9 100644 --- a/libraries/search-solidity/lib/testcase/sampling/SolidityRandomSampler.ts +++ b/libraries/search-solidity/lib/testcase/sampling/SolidityRandomSampler.ts @@ -21,11 +21,10 @@ import { prng } from "@syntest/prng"; import { SoliditySampler } from "./SoliditySampler"; import { AddressStatement } from "../statements/primitive/AddressStatement"; import BigNumber from "bignumber.js"; -import { ArrayStatement } from "../statements/complex/ArrayStatement"; import { SoliditySubject } from "../../search/SoliditySubject"; import { SolidityTestCase } from "../SolidityTestCase"; import { ConstructorCall } from "../statements/action/ConstructorCall"; -import { ObjectFunctionCall } from "../statements/action/ObjectFunctionCall"; +import { ContractFunctionCall } from "../statements/action/ContractFunctionCall"; import { NumericStatement } from "../statements/primitive/NumericStatement"; import { BoolStatement } from "../statements/primitive/BoolStatement"; import { StringStatement } from "../statements/primitive/StringStatement"; @@ -35,21 +34,25 @@ import { Address, Bool, ConstantPool, + Contract, DynamicSizeByteArray, Fixed, FixedSizeByteArray, + FunctionType, Int, Parameter, StringType, TypeEnum, Ufixed, Uint, + isExternal, } from "@syntest/analysis-solidity"; import { TargetType } from "@syntest/analysis"; import { FunctionTarget } from "@syntest/analysis-solidity"; import { IntegerStatement } from "../statements/primitive/IntegerStatement"; import { DynamicSizeByteArrayStatement } from "../statements/primitive/DynamicSizeByteArrayStatement"; import { FixedSizeByteArrayStatement } from "../statements/primitive/FixedSizeByteArrayStatement"; +import { StatementPool } from "../StatementPool"; /** * SolidityRandomSampler class @@ -91,26 +94,82 @@ export class SolidityRandomSampler extends SoliditySampler { } sample(): SolidityTestCase { - const root = this.sampleConstructor(0); + const roots: ActionStatement[] = []; - return new SolidityTestCase(root); + for ( + let index = 0; + index < prng.nextInt(1, this.maxActionStatements); // (i think its better to start with a single statement) + index++ + ) { + this.statementPool = new StatementPool(roots); + roots.push(this.sampleRoot()); + } + this.statementPool = undefined; + + return new SolidityTestCase(roots); + } + + sampleRoot(): ActionStatement { + const targets = (this._subject).getActionableTargets(); + + const action = prng.pickOne( + targets.filter( + (target) => + (target.type === TargetType.CLASS) || + (target.type === TargetType.FUNCTION && isExternal(target)) + ) + ) + + switch (action.type) { + case TargetType.CLASS: { + return this.sampleConstructorCall(0, { + name: action.name, + type: { + type: TypeEnum.CONTRACT, + id: action.id + } + }) + } + case TargetType.FUNCTION: { + if (action.isConstructor) { + return this.sampleConstructorCall(0, { + name: action.name, + type: { + type: TypeEnum.CONTRACT, + id: action.id + } + }) + } + return this.sampleContractFunctionCall(0, { + name: action.name, + type: { + type: TypeEnum.FUNCTION, + parameters: action.parameters, + returns: action.returnParameters, + visibility: action.visibility, + stateMutability: action.mutability + } + }) + } + } } - sampleContractFunction( + sampleContractFunctionCall( depth: number, - root: ConstructorCall - ): ObjectFunctionCall { + type: Parameter + ): ContractFunctionCall { const actions = (this._subject) .getActionableTargetsByType(TargetType.FUNCTION) - .filter((x) => (x).name !== "constructor"); - - // TODO make sure these actions are available on this root + .filter((x) => (x).name !== "constructor") if (actions.length === 0) { throw new Error("There are no functions to test!"); } const action = prng.pickOne(actions); + const contractTarget = (this._subject) + .getActionableTargetsByType(TargetType.FUNCTION) + .find((x) => x.id === action.contractId) const arguments_: Statement[] = []; @@ -118,14 +177,9 @@ export class SolidityRandomSampler extends SoliditySampler { arguments_.push(this.sampleArgument(depth + 1, parameter)); } - const uniqueID = prng.uniqueId(); - // TODO not sure why this is needed - // if (action.returnType == "") uniqueID = "var" + uniqueID; - - return new ObjectFunctionCall( - action.returnParameters, - uniqueID, - root, + return new ContractFunctionCall( + type, + prng.uniqueId(), action.name, arguments_, this.sampleAddressStatement(depth + 1, { @@ -134,29 +188,35 @@ export class SolidityRandomSampler extends SoliditySampler { type: TypeEnum.ADDRESS, stateMutability: undefined } + }), + this.sampleConstructorCall(depth +1, { + name: contractTarget.name, + type: { + type: TypeEnum.CONTRACT, + id: contractTarget.id + } }) ); } - sampleConstructor(depth: number): ConstructorCall { + sampleConstructorCall(depth: number, type: Parameter): ConstructorCall { const constructors = (this._subject) .getActionableTargetsByType(TargetType.FUNCTION) - .filter((x) => (x).name === "constructor"); + .filter((x) => (x).name === "constructor") + .filter((x) => (x).contractId === type.type.id) if (constructors.length > 0) { const action = prng.pickOne(constructors); const arguments_: Statement[] = []; for (const parameter of action.parameters) { - arguments_.push(this.sampleArgument(1, parameter)); + arguments_.push(this.sampleArgument(depth + 1, parameter)); } - const root = new ConstructorCall( - [{ type: action.name, name: "contract" }], + return new ConstructorCall( + type, prng.uniqueId(), - `${action.name}`, arguments_, - [], this.sampleAddressStatement(depth + 1, { name: 'address', type: { @@ -165,22 +225,11 @@ export class SolidityRandomSampler extends SoliditySampler { } }) ); - - const nCalls = prng.nextInt(1, 5); - for (let index = 0; index <= nCalls; index++) { - const call = this.sampleContractFunction(depth + 1, root); - root.setMethodCall(index, call as ActionStatement); - } - - // constructors do not have return parameters... - return root; } else { // if no constructors is available, we invoke the default (implicit) constructor - const root = new ConstructorCall( - [{ type: this._subject.name, name: "contract" }], + return new ConstructorCall( + type, prng.uniqueId(), - `${this._subject.name}`, - [], [], this.sampleAddressStatement(depth + 1, { name: 'address', @@ -190,14 +239,6 @@ export class SolidityRandomSampler extends SoliditySampler { } }) ); - - const nCalls = prng.nextInt(1, 5); - for (let index = 0; index <= nCalls; index++) { - const call = this.sampleContractFunction(depth + 1, root); - root.setMethodCall(index, call as ActionStatement); - } - - return root; } } diff --git a/libraries/search-solidity/lib/testcase/sampling/SoliditySampler.ts b/libraries/search-solidity/lib/testcase/sampling/SoliditySampler.ts index 7665d4a..29891fb 100644 --- a/libraries/search-solidity/lib/testcase/sampling/SoliditySampler.ts +++ b/libraries/search-solidity/lib/testcase/sampling/SoliditySampler.ts @@ -21,7 +21,7 @@ import { Parameter } from "@syntest/analysis-solidity"; import { SolidityTestCase } from "../SolidityTestCase"; import { ConstructorCall } from "../statements/action/ConstructorCall"; -import { ObjectFunctionCall } from "../statements/action/ObjectFunctionCall"; +import { ContractFunctionCall } from "../statements/action/ContractFunctionCall"; import { Statement } from "../statements/Statement"; import { SoliditySubject } from "../../search/SoliditySubject"; import { ConstantPool, RootContext } from "@syntest/analysis-solidity"; @@ -32,6 +32,8 @@ import { IntegerStatement } from "../statements/primitive/IntegerStatement"; import { StringStatement } from "../statements/primitive/StringStatement"; import { FixedSizeByteArrayStatement } from "../statements/primitive/FixedSizeByteArrayStatement"; import { DynamicSizeByteArrayStatement } from "../statements/primitive/DynamicSizeByteArrayStatement"; +import { ActionStatement } from "../statements/action/ActionStatement"; +import { StatementPool } from "../StatementPool"; /** * SolidityRandomSampler class @@ -108,11 +110,13 @@ export abstract class SoliditySampler extends EncodingSampler this._statementPool = statementPool; } - abstract sampleConstructor(depth: number): ConstructorCall; - abstract sampleContractFunction( + abstract sampleRoot(): ActionStatement; + + abstract sampleConstructorCall(depth: number, type: Parameter): ConstructorCall; + abstract sampleContractFunctionCall( depth: number, - root: ConstructorCall - ): ObjectFunctionCall; + type: Parameter + ): ContractFunctionCall; abstract sampleArgument(depth: number, type: Parameter): Statement; diff --git a/libraries/search-solidity/lib/testcase/statements/RootStatement.ts b/libraries/search-solidity/lib/testcase/statements/RootStatement.ts deleted file mode 100644 index e9b381b..0000000 --- a/libraries/search-solidity/lib/testcase/statements/RootStatement.ts +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright 2020-2022 Delft University of Technology and SynTest contributors - * - * This file is part of SynTest Solidity. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { Statement } from "./Statement"; -import { ActionStatement } from "./action/ActionStatement"; -import { Parameter } from "@syntest/analysis-solidity"; - -/** - * RootStatement - */ -export abstract class RootStatement extends ActionStatement { - private _children: Statement[]; - - protected constructor( - types: Parameter[], - uniqueId: string, - arguments_: Statement[], - children: Statement[] - ) { - super(types, uniqueId, arguments_); - this._children = children; - } - - override hasChildren(): boolean { - return this._children.length > 0 || this.arguments_.length > 0; - } - - override getChildren(): Statement[] { - return [...this._children, ...this.arguments_]; - } - - get children(): Statement[] { - return this._children; - } -} diff --git a/libraries/search-solidity/lib/testcase/statements/Statement.ts b/libraries/search-solidity/lib/testcase/statements/Statement.ts index c60d99c..919dbb4 100644 --- a/libraries/search-solidity/lib/testcase/statements/Statement.ts +++ b/libraries/search-solidity/lib/testcase/statements/Statement.ts @@ -17,37 +17,32 @@ */ import { EncodingSampler, Encoding } from "@syntest/search"; -import { Parameter } from "@syntest/analysis-solidity"; +import { Parameter, Type } from "@syntest/analysis-solidity"; +import { Decoding } from "../../testbuilding/Decoding"; +import { ContextBuilder } from "../../testbuilding/ContextBuilder"; /** * Statement */ -export abstract class Statement { - public get varNames(): string[] { - return this._varNames; - } +export abstract class Statement { public get uniqueId(): string { return this._uniqueId; } - public get types(): Parameter[] { - return this._types; + public get type(): Parameter { + return this._type; } - private _varNames: string[]; - private _types: Parameter[]; + private _type: Parameter; private _uniqueId: string; /** * Constructor - * @param types + * @param type * @param uniqueId */ - protected constructor(types: Parameter[], uniqueId: string) { - this._types = types; + protected constructor(type: Parameter, uniqueId: string) { + this._type = type; this._uniqueId = uniqueId; - this._varNames = types.map((x) => { - return x.name + uniqueId; - }); } /** @@ -62,7 +57,7 @@ export abstract class Statement { * Creates an exact copy of the current gene * @return the copy of the gene */ - abstract copy(): Statement; + abstract copy(): Statement; /** * Checks whether the gene has children @@ -75,4 +70,22 @@ export abstract class Statement { * @return The set of children of this gene */ abstract getChildren(): Statement[]; + + /** + * Set a new child at a specified position + * + * WARNING: This function has side effects + * + * @param index the index position of the new child + * @param newChild the new child + */ + abstract setChild(index: number, child: Statement): void; + + /** + * Decodes the statement + */ + abstract decode( + context: ContextBuilder, + exception: boolean + ): Decoding[]; } diff --git a/libraries/search-solidity/lib/testcase/statements/action/ActionStatement.ts b/libraries/search-solidity/lib/testcase/statements/action/ActionStatement.ts index 2c857ea..37d0f8c 100644 --- a/libraries/search-solidity/lib/testcase/statements/action/ActionStatement.ts +++ b/libraries/search-solidity/lib/testcase/statements/action/ActionStatement.ts @@ -16,24 +16,28 @@ * limitations under the License. */ +import { SoliditySampler } from "../../sampling/SoliditySampler"; import { Statement } from "../Statement"; -import { Parameter } from "@syntest/analysis-solidity"; +import { Parameter, Type } from "@syntest/analysis-solidity"; /** - * @author Dimitri Stallenberg + * ActionStatement */ -export abstract class ActionStatement extends Statement { +export abstract class ActionStatement extends Statement { private _arguments_: Statement[]; protected constructor( - types: Parameter[], + type: Parameter, uniqueId: string, arguments_: Statement[] ) { - super(types, uniqueId); + super(type, uniqueId); this._arguments_ = arguments_; } + abstract override mutate(sampler: SoliditySampler, depth: number): ActionStatement + abstract override copy(): ActionStatement + hasChildren(): boolean { return this._arguments_.length > 0; } diff --git a/libraries/search-solidity/lib/testcase/statements/action/ConstructorCall.ts b/libraries/search-solidity/lib/testcase/statements/action/ConstructorCall.ts index 76ed1a1..5e3b208 100644 --- a/libraries/search-solidity/lib/testcase/statements/action/ConstructorCall.ts +++ b/libraries/search-solidity/lib/testcase/statements/action/ConstructorCall.ts @@ -21,19 +21,15 @@ import { prng } from "@syntest/prng"; import { SoliditySampler } from "../../sampling/SoliditySampler"; import { ActionStatement } from "./ActionStatement"; import { Statement } from "../Statement"; -import { Parameter } from "@syntest/analysis-solidity"; +import { Contract, Parameter } from "@syntest/analysis-solidity"; +import { ContextBuilder } from "../../../testbuilding/ContextBuilder"; +import { Decoding } from "../../../testbuilding/Decoding"; +import { shouldNeverHappen } from "@syntest/search"; /** - * @author Dimitri Stallenberg - * @author Annibale Panichella + * ConstructorCall */ -export class ConstructorCall extends ActionStatement { - get constructorName(): string { - return this._constructorName; - } - - private readonly _constructorName: string; - private readonly _calls: ActionStatement[]; +export class ConstructorCall extends ActionStatement { private _sender: AddressStatement; /** @@ -46,114 +42,98 @@ export class ConstructorCall extends ActionStatement { * @param sender the sender of the message */ constructor( - types: Parameter[], + type: Parameter, uniqueId: string, - constructorName: string, arguments_: Statement[], - calls: ActionStatement[], sender: AddressStatement ) { - super(types, uniqueId, arguments_); - this._constructorName = constructorName; - this._calls = calls; + super(type, uniqueId, arguments_); this._sender = sender; } mutate(sampler: SoliditySampler, depth: number) { - if (this.arguments_.length > 0) { + if (sampler.deltaMutationProbability) { const arguments_ = this.arguments_.map((a: Statement) => a.copy()); - const index = prng.nextInt(0, arguments_.length - 1); - if (arguments_[index] !== undefined) - arguments_[index] = arguments_[index].mutate(sampler, depth + 1); - } - - let changed = false; - if (prng.nextDouble(0, 1) <= 1 / 3 && this.getMethodCalls().length > 1) { - this.deleteMethodCall(); - changed = true; - } - if (prng.nextDouble(0, 1) <= 1 / 3) { - this.replaceMethodCall(sampler, depth); - changed = true; - } - if (prng.nextDouble(0, 1) <= 1 / 3) { - this.addMethodCall(sampler, depth); - changed = true; - } - - if (!this.hasMethodCalls()) { - this.addMethodCall(sampler, depth); - changed = true; - } - - if (!changed) { - this.replaceMethodCall(sampler, depth); - this.addMethodCall(sampler, depth); - } - - return this; - } - - protected addMethodCall(sampler: SoliditySampler, depth: number) { - let count = 0; - while (prng.nextDouble(0, 1) <= Math.pow(0.5, count) && count < 10) { - const index = prng.nextInt(0, this._calls.length); - - this._calls.splice( - index, - 0, - sampler.sampleContractFunction(depth + 1, this) + let sender = this._sender.copy() + + if (arguments_.length > 0) { + const index = prng.nextInt(0, arguments_.length + 1); + if (arguments_[index] === undefined) { + sender = sender.mutate(sampler, depth + 1) + } else { + arguments_[index] = arguments_[index].mutate(sampler, depth + 1); + } + } else { + sender = sender.mutate(sampler, depth + 1) + } + + return new ConstructorCall( + this.type, + prng.uniqueId(), + this.arguments_, + sender ); - count++; - } - } - - protected replaceMethodCall(sampler: SoliditySampler, depth: number) { - if (this.hasMethodCalls()) { - const calls = this.getMethodCalls(); - const index = prng.nextInt(0, calls.length - 1); - this.setMethodCall(index, calls[index].mutate(sampler, depth)); - } - } - - protected deleteMethodCall() { - if (this.hasMethodCalls()) { - const calls = this.getMethodCalls(); - const index = prng.nextInt(0, calls.length - 1); - this._calls.splice(index, 1); + } else { + return sampler.sampleConstructorCall(depth, this.type) } } copy() { const deepCopyArguments = this.arguments_.map((a: Statement) => a.copy()); - const deepCopyCalls = this._calls.map((a: ActionStatement) => a.copy()); return new ConstructorCall( - this.types, + this.type, this.uniqueId, - this.constructorName, deepCopyArguments, - deepCopyCalls, this._sender.copy() ); } - getMethodCalls(): ActionStatement[] { - return [...this._calls]; + override hasChildren(): boolean { + // since every object function call has an instance there must be atleast one child + return true; } - setMethodCall(index: number, call: ActionStatement) { - this._calls[index] = call; + override getChildren(): Statement[] { + return [...this.arguments_, this._sender]; } - hasMethodCalls(): boolean { - return this._calls.length > 0; - } + override setChild(index: number, newChild: Statement) { + if (!newChild) { + throw new Error("Invalid new child!"); + } - setSender(sender: AddressStatement) { - this._sender = sender; + if (index < 0 || index > this.arguments_.length) { + throw new Error(shouldNeverHappen(`Invalid index used index: ${index}`)); + } + + if (index === this.arguments_.length) { + if (!(newChild instanceof AddressStatement)) { + throw new TypeError(shouldNeverHappen("should be a constructor")); + } + this._sender = newChild; + } else { + this.arguments_[index] = newChild; + } } - getSender(): AddressStatement { - return this._sender; + decode(context: ContextBuilder, exception: boolean): Decoding[] { + const senderName = context.getOrCreateVariableName(this._sender.type) + const argumentNames = this.arguments_.map((a) => context.getOrCreateVariableName(a.type)).join(", "); + + const senderString = argumentNames == "" ? `{ from: ${senderName} }` : `, { from: ${senderName} }` + + const senderDecoding: Decoding[] = this._sender.decode(context) + const argumentDecodings: Decoding[] = this.arguments_.flatMap((a) => a.decode(context, exception)) + + const decoded = exception ? `await expect(${this.type.name}.new(${argumentNames}${senderString})).to.be.rejectedWith(Error);` : `const ${context.getOrCreateVariableName(this.type)} = await ${this.type.name}.new(${argumentNames}${senderString});`; + + return [ + ...senderDecoding, + ...argumentDecodings, + { + decoded: decoded, + reference: this, + }, + ]; } } diff --git a/libraries/search-solidity/lib/testcase/statements/action/ContractFunctionCall.ts b/libraries/search-solidity/lib/testcase/statements/action/ContractFunctionCall.ts new file mode 100644 index 0000000..dd61fae --- /dev/null +++ b/libraries/search-solidity/lib/testcase/statements/action/ContractFunctionCall.ts @@ -0,0 +1,164 @@ +/* + * Copyright 2020-2022 Delft University of Technology and SynTest contributors + * + * This file is part of SynTest Solidity. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { ConstructorCall } from "./ConstructorCall"; +import { AddressStatement } from "../primitive/AddressStatement"; + +import { prng } from "@syntest/prng"; +import { SoliditySampler } from "../../sampling/SoliditySampler"; +import { ActionStatement } from "./ActionStatement"; +import { Statement } from "../Statement"; +import { Parameter, FunctionType } from "@syntest/analysis-solidity"; +import { ContextBuilder } from "../../../testbuilding/ContextBuilder"; +import { Decoding } from "../../../testbuilding/Decoding"; +import { shouldNeverHappen } from "@syntest/search"; + +export class ContractFunctionCall extends ActionStatement { + private readonly _functionName: string; + private _sender: AddressStatement; + + private _constructor: ConstructorCall; + + /** + * Constructor + * @param types the return types of the function + * @param uniqueId id of the gene + * @param constructor the object to call the function on + * @param functionName the name of the function + * @param args the arguments of the function + */ + constructor( + type: Parameter, + uniqueId: string, + functionName: string, + arguments_: Statement[], + sender: AddressStatement, + constructor: ConstructorCall + ) { + super(type, uniqueId, [...arguments_]); + this._functionName = functionName; + this._sender = sender; + this._constructor = constructor; + } + + mutate(sampler: SoliditySampler, depth: number): ContractFunctionCall { + if (prng.nextBoolean(sampler.deltaMutationProbability)) { + const arguments_ = this.arguments_.map((a: Statement) => a.copy()); + let constructor_ = this._constructor.copy() + let sender = this._sender.copy() + + const index = prng.nextInt(0, arguments_.length + 1); + + if (index < arguments_.length) { + arguments_[index] = arguments_[index].mutate(sampler, depth + 1); + } else if (index === arguments_.length) { + constructor_ = constructor_.mutate(sampler, depth + 1); + } else { + sender = sender.mutate(sampler, depth + 1) + } + + return new ContractFunctionCall( + this.type, + this.uniqueId, + this._functionName, + arguments_, + sender, + constructor_ + ); + } else { + // resample the gene + return sampler.sampleContractFunctionCall(depth, this.type) + } + } + + copy() { + const deepCopyArguments = this.arguments_.map((a: Statement) => a.copy()); + + return new ContractFunctionCall( + this.type, + this.uniqueId, + this._functionName, + deepCopyArguments, + this._sender.copy(), + this._constructor.copy() + ); + } + + override hasChildren(): boolean { + // since every object function call has an instance there must be atleast one child + return true; + } + + override getChildren(): Statement[] { + return [...this.arguments_, this._sender, this._constructor]; + } + + override setChild(index: number, newChild: Statement) { + if (!newChild) { + throw new Error("Invalid new child!"); + } + + if (index < 0 || index > this.arguments_.length) { + throw new Error(shouldNeverHappen(`Invalid index used index: ${index}`)); + } + + if (index === this.arguments_.length + 1) { + if (!(newChild instanceof ConstructorCall)) { + throw new TypeError(shouldNeverHappen("should be a constructor")); + } + this._constructor = newChild; + } else if (index === this.arguments_.length) { + if (!(newChild instanceof AddressStatement)) { + throw new TypeError(shouldNeverHappen("should be a constructor")); + } + this._sender = newChild; + } else { + this.arguments_[index] = newChild; + } + } + + decode(context: ContextBuilder, exception: boolean): Decoding[] { + const constructorName = context.getOrCreateVariableName(this._constructor.type) + const senderName = context.getOrCreateVariableName(this._sender.type) + const argumentNames = this.arguments_.map((a) => context.getOrCreateVariableName(a.type)).join(", "); + + const returnValues: string[] = this.type.type.returns.map((returnValue) => context.getOrCreateVariableName(returnValue)) + const senderString = argumentNames == "" ? `{ from: ${senderName} }` : `, { from: ${senderName} }` + + const constructorDecoding: Decoding[] = this._constructor.decode(context, exception) + const senderDecoding: Decoding[] = this._sender.decode(context) + const argumentDecodings: Decoding[] = this.arguments_.flatMap((a) => a.decode(context, exception)) + + let decoded: string + if (exception) { + decoded = returnValues.length > 0 ? `await expect(${constructorName}.${this._functionName}.call(${argumentNames}${senderString})).to.be.rejectedWith(Error);` : `await expect(${constructorName}.${this._functionName}.call(${argumentNames}${senderString})).to.be.rejectedWith(Error);`; + } else { + decoded = returnValues.length > 0 ? `const [${returnValues.join(', ') }] = await ${constructorName}.${this._functionName}.call(${argumentNames}${senderString});` : `await ${constructorName}.${this._functionName}.call(${argumentNames}${senderString});`; + } + + return [ + ...constructorDecoding, + ...senderDecoding, + ...argumentDecodings, + { + decoded: decoded, + reference: this, + }, + ]; + } +} diff --git a/libraries/search-solidity/lib/testcase/statements/action/FunctionCall.ts b/libraries/search-solidity/lib/testcase/statements/action/FunctionCall.ts deleted file mode 100644 index bbf6f46..0000000 --- a/libraries/search-solidity/lib/testcase/statements/action/FunctionCall.ts +++ /dev/null @@ -1,93 +0,0 @@ -/* - * Copyright 2020-2022 Delft University of Technology and SynTest contributors - * - * This file is part of SynTest Solidity. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { prng } from "@syntest/prng"; -import { SoliditySampler } from "../../sampling/SoliditySampler"; -import { ActionStatement } from "./ActionStatement"; -import { Statement } from "../Statement"; -import { Parameter } from "@syntest/analysis-solidity"; - -/** - * @author Dimitri Stallenberg - */ -export class FunctionCall extends ActionStatement { - get functionName(): string { - return this._functionName; - } - - private readonly _functionName: string; - - /** - * Constructor - * @param types the return types of the function - * @param uniqueId id of the gene - * @param functionName the name of the function - * @param args the arguments of the function - */ - constructor( - types: Parameter[], - uniqueId: string, - functionName: string, - arguments_: Statement[] - ) { - super(types, uniqueId, [...arguments_]); - this._functionName = functionName; - } - - mutate(sampler: SoliditySampler, depth: number) { - if (prng.nextBoolean(sampler.deltaMutationProbability)) { - if (this.arguments_.length === 0) { - return this.copy(); - } - - // randomly mutate one of the args - const arguments_ = this.arguments_.map((a: Statement) => a.copy()); - const index = prng.nextInt(0, arguments_.length - 1); - arguments_[index] = arguments_[index].mutate(sampler, depth + 1); - - return new FunctionCall( - this.types, - this.uniqueId, - this.functionName, - arguments_ - ); - } else { - // resample the gene - return sampler.sampleStatement(depth, this.types, "functionCall"); - } - } - - copy(): FunctionCall { - const deepCopyArguments = this.arguments_.map((a: Statement) => a.copy()); - - return new FunctionCall( - this.types, - this.uniqueId, - this.functionName, - deepCopyArguments - ); - } - - hasChildren(): boolean { - return this.arguments_.length > 0; - } - - getChildren(): Statement[] { - return [...this.arguments_]; - } -} diff --git a/libraries/search-solidity/lib/testcase/statements/action/ObjectFunctionCall.ts b/libraries/search-solidity/lib/testcase/statements/action/ObjectFunctionCall.ts deleted file mode 100644 index 4bba533..0000000 --- a/libraries/search-solidity/lib/testcase/statements/action/ObjectFunctionCall.ts +++ /dev/null @@ -1,120 +0,0 @@ -/* - * Copyright 2020-2022 Delft University of Technology and SynTest contributors - * - * This file is part of SynTest Solidity. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { ConstructorCall } from "./ConstructorCall"; -import { AddressStatement } from "../primitive/AddressStatement"; - -import { prng } from "@syntest/prng"; -import { SoliditySampler } from "../../sampling/SoliditySampler"; -import { ActionStatement } from "./ActionStatement"; -import { Statement } from "../Statement"; -import { Parameter } from "@syntest/analysis-solidity"; -/** - * @author Dimitri Stallenberg - */ -export class ObjectFunctionCall extends ActionStatement { - private readonly _functionName: string; - private _sender: AddressStatement; - - private readonly _parent: ConstructorCall; - - /** - * Constructor - * @param types the return types of the function - * @param uniqueId id of the gene - * @param instance the object to call the function on - * @param functionName the name of the function - * @param args the arguments of the function - */ - constructor( - types: Parameter[], - uniqueId: string, - instance: ConstructorCall, - functionName: string, - arguments_: Statement[], - sender: AddressStatement - ) { - super(types, uniqueId, [...arguments_]); - this._parent = instance; - this._functionName = functionName; - this._sender = sender; - } - - mutate(sampler: SoliditySampler, depth: number): ObjectFunctionCall { - if (prng.nextBoolean(sampler.deltaMutationProbability)) { - const arguments_ = this.arguments_.map((a: Statement) => a.copy()); - if (arguments_.length === 0) return this; - - const index = prng.nextInt(0, arguments_.length - 1); - arguments_[index] = arguments_[index].mutate(sampler, depth + 1); - - const instance = this._parent; - return new ObjectFunctionCall( - this.types, - this.uniqueId, - instance, - this.functionName, - arguments_, - this._sender.copy() - ); - } else { - // resample the gene - return ( - sampler.sampleStatement(depth, this.types, "functionCall") - ); - } - } - - copy() { - const deepCopyArguments = this.arguments_.map((a: Statement) => a.copy()); - - return new ObjectFunctionCall( - this.types, - this.uniqueId, - this._parent, - this.functionName, - deepCopyArguments, - this._sender.copy() - ); - } - - override hasChildren(): boolean { - // since every object function call has an instance there must be atleast one child - return true; - } - - override getChildren(): Statement[] { - return [...this.arguments_]; - } - - getParent(): ConstructorCall { - return this._parent; - } - - get functionName(): string { - return this._functionName; - } - - setSender(sender: AddressStatement) { - this._sender = sender; - } - - getSender(): AddressStatement { - return this._sender; - } -} diff --git a/libraries/search-solidity/lib/testcase/statements/complex/ArrayStatement.ts b/libraries/search-solidity/lib/testcase/statements/complex/ArrayStatement.ts index 059e9e2..3d88827 100644 --- a/libraries/search-solidity/lib/testcase/statements/complex/ArrayStatement.ts +++ b/libraries/search-solidity/lib/testcase/statements/complex/ArrayStatement.ts @@ -17,40 +17,120 @@ */ import { prng } from "@syntest/prng"; -import { PrimitiveStatement } from "../primitive/PrimitiveStatement"; import { ArrayType, Parameter } from "@syntest/analysis-solidity"; import { SoliditySampler } from "../../sampling/SoliditySampler"; import { Statement } from "../Statement"; +import { ContextBuilder } from "../../../testbuilding/ContextBuilder"; +import { Decoding } from "../../../testbuilding/Decoding"; +import { shouldNeverHappen } from "@syntest/search"; /** * Special statement specific to solidity contracts */ -export class ArrayStatement extends PrimitiveStatement { - private static _upper_bound = 32; - private static _lower_bound = 0; +export class ArrayStatement extends Statement { + private _elements: Statement[] - constructor(type: Parameter, uniqueId: string, bytes: number[]) { - super(type, uniqueId, bytes); + constructor(type: Parameter, uniqueId: string, elements: Statement[]) { + super(type, uniqueId, ); + this._elements = elements } copy() { - return new ArrayStatement(this.type, this.uniqueId, [...this.value]); + return new ArrayStatement(this.type, this.uniqueId, this._elements.map((element) => element.copy())); } mutate(sampler: SoliditySampler, depth: number): Statement { if (prng.nextBoolean(sampler.deltaMutationProbability)) { - const index = prng.nextInt(0, this.value.length - 1); + const children = this._elements.map((a: Statement) => a.copy()); - const change = prng.nextGaussian(0, 3); - const newBytes = [...this.value]; + const choice = prng.nextDouble(); - const newValue = Math.round(newBytes[index] + change); - newBytes[index] = Math.max(ArrayStatement._lower_bound, newValue); - newBytes[index] = Math.min(ArrayStatement._upper_bound, newValue); + if (children.length > 0) { + if (choice < 0.33) { + // 33% chance to add a child on this position + const index = prng.nextInt(0, children.length); + children.splice( + index, + 0, + sampler.sampleArgument(depth + 1, { + name: `${index}`, + type: this.type.type.baseType + }) + ); + } else if (choice < 0.66) { + // 33% chance to remove a child on this position + const index = prng.nextInt(0, children.length - 1); + children.splice(index, 1); + } else { + // 33% chance to mutate a child on this position + const index = prng.nextInt(0, children.length - 1); + children.splice( + index, + 1, + sampler.sampleArgument(depth + 1, { + name: `${index}`, + type: this.type.type.baseType + }) + ); + } + } else { + // no children found so we always add + children.push( + sampler.sampleArgument(depth + 1, { + name: `${0}`, + type: this.type.type.baseType + }) + ); + } - return new ArrayStatement(this.type, prng.uniqueId(), newBytes); + return new ArrayStatement( + this.type, + prng.uniqueId(), + children + ); } else { - return sampler.sampleArgument(depth, this.type) + return sampler.sampleArgument( + depth, + this.type + ); + + } + } + + override hasChildren(): boolean { + // since every object function call has an instance there must be atleast one child + return this._elements.length > 0; + } + + override getChildren(): Statement[] { + return [...this._elements]; + } + + setChild(index: number, newChild: Statement) { + if (!newChild) { + throw new Error("Invalid new child!"); } + + if (index < 0 || index >= this._elements.length) { + throw new Error(shouldNeverHappen(`Invalid index used index: ${index}`)); + } + + this._elements[index] = newChild; + } + + decode(context: ContextBuilder, exception: boolean): Decoding[] { + const childNames = this._elements.map((a) => context.getOrCreateVariableName(a.type)).join(", "); + + const childDecodings: Decoding[] = this._elements.flatMap((a) => a.decode(context, exception)) + + const decoded = `const ${context.getOrCreateVariableName(this.type)} = [${childNames}];` + + return [ + ...childDecodings, + { + decoded: decoded, + reference: this, + }, + ]; } } diff --git a/libraries/search-solidity/lib/testcase/statements/complex/MappingStatement.ts b/libraries/search-solidity/lib/testcase/statements/complex/MappingStatement.ts index c04e8e4..476f5c9 100644 --- a/libraries/search-solidity/lib/testcase/statements/complex/MappingStatement.ts +++ b/libraries/search-solidity/lib/testcase/statements/complex/MappingStatement.ts @@ -17,40 +17,174 @@ */ import { prng } from "@syntest/prng"; -import { PrimitiveStatement } from "../primitive/PrimitiveStatement"; import { Mapping, Parameter } from "@syntest/analysis-solidity"; import { SoliditySampler } from "../../sampling/SoliditySampler"; import { Statement } from "../Statement"; +import { ContextBuilder } from "../../../testbuilding/ContextBuilder"; +import { Decoding } from "../../../testbuilding/Decoding"; +import { shouldNeverHappen } from "@syntest/search"; /** * Special statement specific to solidity contracts */ -export class MappingStatement extends PrimitiveStatement { - private static _upper_bound = 32; - private static _lower_bound = 0; +// TODO wrong could be other type then string! +type MappingType = { + [key: string]: Statement | undefined; +}; +export class MappingStatement extends Statement { + private _mapping: MappingType - constructor(type: Parameter, uniqueId: string, bytes: number[]) { - super(type, uniqueId, bytes); - } - - copy() { - return new MappingStatement(this.type, this.uniqueId, [...this.value]); + constructor(type: Parameter, uniqueId: string, mapping: MappingType) { + super(type, uniqueId); + this._mapping = mapping } mutate(sampler: SoliditySampler, depth: number): Statement { if (prng.nextBoolean(sampler.deltaMutationProbability)) { - const index = prng.nextInt(0, this.value.length - 1); + // 80% + const object: MappingType = {}; + + const keys = Object.keys(this._mapping); - const change = prng.nextGaussian(0, 3); - const newBytes = [...this.value]; + if (keys.length === 0) { + return new MappingStatement( + this.type, + prng.uniqueId(), + object + ); + } - const newValue = Math.round(newBytes[index] + change); - newBytes[index] = Math.max(MappingStatement._lower_bound, newValue); - newBytes[index] = Math.min(MappingStatement._upper_bound, newValue); + const availableKeys = []; + for (const key of keys) { + if (!this._mapping[key]) { + object[key] = undefined; + continue; + } + object[key] = this._mapping[key].copy(); + availableKeys.push(key); + } - return new MappingStatement(this.type, prng.uniqueId(), newBytes); + const choice = prng.nextDouble(); + + if (availableKeys.length > 0) { + if (choice < 0.33) { + // 33% chance to add a child on this position + const index = prng.nextInt(0, keys.length - 1); + const key = keys[index]; + object[key] = sampler.sampleArgument( + depth + 1, + { + name: key, + type: this.type.type.valueType + } + ); + } else if (choice < 0.66) { + // 33% chance to remove a child on this position + const key = prng.pickOne(availableKeys); + object[key] = undefined; + } else { + // 33% chance to mutate a child + const key = prng.pickOne(availableKeys); + object[key] = object[key].mutate(sampler, depth + 1); + } + } else { + // no keys available so we add one + const index = prng.nextInt(0, keys.length - 1); + const key = keys[index]; + object[key] = sampler.sampleArgument( + depth + 1, + { + name: key, + type: this.type.type.valueType + } + ); + } + + return new MappingStatement( + this.type, + prng.uniqueId(), + object + ); } else { - return sampler.sampleArgument(depth, this.type) + return sampler.sampleArgument( + depth, + this.type + ); + } + } + + copy() { + const mapping: MappingType = {}; + + for (const key of Object.keys(this._mapping)) { + if (this._mapping[key] === undefined) { + mapping[key] = undefined; + continue; + } + if (this._mapping[key].uniqueId === this.uniqueId) { + console.log("circular detected"); + mapping[key] = undefined; + continue; + } + mapping[key] = this._mapping[key].copy(); } + + return new MappingStatement( + this.type, + this.uniqueId, + mapping + ); + } + + getChildren(): Statement[] { + return Object.keys(this._mapping) + .sort() + .filter((key) => this._mapping[key] !== undefined) + .map((key) => this._mapping[key]); + } + + hasChildren(): boolean { + return this.getChildren().length > 0; + } + + override setChild(index: number, child: Statement): void { + if (!child) { + throw new Error("Invalid new child!"); + } + + if (index < 0 || index >= this.getChildren().length) { + throw new Error(shouldNeverHappen(`Invalid index used index: ${index}`)); + } + + const keys = Object.keys(this._mapping) + .sort() + .filter((key) => this._mapping[key] !== undefined); + const key = keys[index]; + + this._mapping[key] = child; + } + + decode( + context: ContextBuilder, + exception: boolean + ): Decoding[] { + const childNames = Object.keys(this._mapping) + .filter((key) => this._mapping[key] !== undefined) + .map((key) => `\t\t\t"${key}": ${context.getOrCreateVariableName(this._mapping[key].type)}`) + .join(",\n"); + + const childDecodings: Decoding[] = Object.values(this._mapping) + .filter((a) => a !== undefined) + .flatMap((a) => a.decode(context, exception)); + + const decoded = `const ${context.getOrCreateVariableName(this.type)} = {\n${childNames}\n\t\t}`; + + return [ + ...childDecodings, + { + decoded: decoded, + reference: this, + }, + ]; } } diff --git a/libraries/search-solidity/lib/testcase/statements/primitive/AddressStatement.ts b/libraries/search-solidity/lib/testcase/statements/primitive/AddressStatement.ts index 64dc7c2..da20342 100644 --- a/libraries/search-solidity/lib/testcase/statements/primitive/AddressStatement.ts +++ b/libraries/search-solidity/lib/testcase/statements/primitive/AddressStatement.ts @@ -20,6 +20,9 @@ import { prng } from "@syntest/prng"; import { Address, Parameter } from "@syntest/analysis-solidity"; import { SoliditySampler } from "../../sampling/SoliditySampler"; import { PrimitiveStatement } from "./PrimitiveStatement"; +import { Statement } from "../Statement"; +import { ContextBuilder } from "../../../testbuilding/ContextBuilder"; +import { Decoding } from "../../../testbuilding/Decoding"; /** * Special statement specific to solidity contracts @@ -28,7 +31,7 @@ export class AddressStatement extends PrimitiveStatement { private readonly _account: number; constructor( - type: Parameter, + type: Parameter
, uniqueId: string, value: string, account: number @@ -37,7 +40,7 @@ export class AddressStatement extends PrimitiveStatement { this._account = account; } - mutate(sampler: SoliditySampler, depth: number): AddressStatement { + mutate(sampler: SoliditySampler, depth: number): Statement { if (prng.nextBoolean(sampler.deltaMutationProbability)) { if (this.value.startsWith("0x")) { const newAccount = prng.nextBoolean(0.5) @@ -66,7 +69,7 @@ export class AddressStatement extends PrimitiveStatement { this._account - 1 ); } else { - return sampler.sampleArgument(depth, this.types); + return sampler.sampleArgument(depth, this.type); } } @@ -83,16 +86,24 @@ export class AddressStatement extends PrimitiveStatement { return this._account; } - public toCode(): string { - if (this.value.startsWith("0x")) - return `const ${this.varName} = "${this.value}"`; - - return `const ${this.varName} = ${this.value}`; - } public getValue(): string { if (this.value.startsWith("0x")) return `"${this.value}"`; return `${this.value}`; } + + decode(context: ContextBuilder): Decoding[] { + const variableName = context.getOrCreateVariableName(this.type) + let decoded = `const ${variableName} = ${this.value};` + if (this.value.startsWith('0x')) { + decoded = `const ${variableName} = "${this.value}";` + } + return [ + { + decoded: decoded, + reference: this, + }, + ]; + } } diff --git a/libraries/search-solidity/lib/testcase/statements/primitive/BoolStatement.ts b/libraries/search-solidity/lib/testcase/statements/primitive/BoolStatement.ts index fd9f5b1..f7309bd 100644 --- a/libraries/search-solidity/lib/testcase/statements/primitive/BoolStatement.ts +++ b/libraries/search-solidity/lib/testcase/statements/primitive/BoolStatement.ts @@ -20,22 +20,34 @@ import { prng } from "@syntest/prng"; import { Bool, Parameter } from "@syntest/analysis-solidity"; import { PrimitiveStatement } from "./PrimitiveStatement"; import { SoliditySampler } from "../../sampling/SoliditySampler"; +import { Statement } from "../Statement"; +import { ContextBuilder } from "../../../testbuilding/ContextBuilder"; +import { Decoding } from "../../../testbuilding/Decoding"; /** * @author Dimitri Stallenberg */ export class BoolStatement extends PrimitiveStatement { - constructor(type: Parameter, uniqueId: string, value: boolean) { + constructor(type: Parameter, uniqueId: string, value: boolean) { super(type, uniqueId, value); } - mutate(sampler: SoliditySampler) { + mutate(sampler: SoliditySampler, depth: number): Statement { return prng.nextBoolean(sampler.deltaMutationProbability) ? new BoolStatement(this.type, this.uniqueId, !this.value) - : BoolStatement.getRandom(this.type); + : sampler.sampleArgument(depth, this.type) } copy() { return new BoolStatement(this.type, this.uniqueId, this.value); } + + decode(context: ContextBuilder): Decoding[] { + return [ + { + decoded: `const ${context.getOrCreateVariableName(this.type)} = ${this.value};`, + reference: this, + }, + ]; + } } diff --git a/libraries/search-solidity/lib/testcase/statements/primitive/DynamicSizeByteArrayStatement.ts b/libraries/search-solidity/lib/testcase/statements/primitive/DynamicSizeByteArrayStatement.ts index a2bc068..4b847dc 100644 --- a/libraries/search-solidity/lib/testcase/statements/primitive/DynamicSizeByteArrayStatement.ts +++ b/libraries/search-solidity/lib/testcase/statements/primitive/DynamicSizeByteArrayStatement.ts @@ -21,6 +21,9 @@ import { PrimitiveStatement } from "./PrimitiveStatement"; import { DynamicSizeByteArray, Parameter } from "@syntest/analysis-solidity"; import { SoliditySampler } from "../../sampling/SoliditySampler"; import { Statement } from "../Statement"; +import { ContextBuilder } from "../../../testbuilding/ContextBuilder"; +import * as web3_utils from "web3-utils"; +import { Decoding } from "../../../testbuilding/Decoding"; /** * Special statement specific to solidity contracts @@ -32,7 +35,7 @@ export class DynamicSizeByteArrayStatement extends PrimitiveStatement< public static upper_bound = 256; public static lower_bound = 0; - constructor(type: Parameter, uniqueId: string, bytes: number[]) { + constructor(type: Parameter, uniqueId: string, bytes: number[]) { super(type, uniqueId, bytes); } @@ -68,4 +71,15 @@ export class DynamicSizeByteArrayStatement extends PrimitiveStatement< return sampler.sampleArgument(depth, this.type); } } + + decode(context: ContextBuilder): Decoding[] { + const bytes = web3_utils.bytesToHex(this.value); + + return [ + { + decoded: `const ${context.getOrCreateVariableName(this.type)} = "${bytes}";`, + reference: this, + }, + ]; + } } diff --git a/libraries/search-solidity/lib/testcase/statements/primitive/FixedSizeByteArrayStatement.ts b/libraries/search-solidity/lib/testcase/statements/primitive/FixedSizeByteArrayStatement.ts index 4717ebc..09fe0b0 100644 --- a/libraries/search-solidity/lib/testcase/statements/primitive/FixedSizeByteArrayStatement.ts +++ b/libraries/search-solidity/lib/testcase/statements/primitive/FixedSizeByteArrayStatement.ts @@ -21,6 +21,9 @@ import { PrimitiveStatement } from "../primitive/PrimitiveStatement"; import { FixedSizeByteArray, Parameter } from "@syntest/analysis-solidity"; import { SoliditySampler } from "../../sampling/SoliditySampler"; import { Statement } from "../Statement"; +import { Decoding } from "../../../testbuilding/Decoding"; +import { ContextBuilder } from "../../../testbuilding/ContextBuilder"; +import * as web3_utils from "web3-utils"; /** * Special statement specific to solidity contracts @@ -32,7 +35,7 @@ export class FixedSizeByteArrayStatement extends PrimitiveStatement< public static upper_bound = 256; public static lower_bound = 0; - constructor(type: Parameter, uniqueId: string, bytes: number[]) { + constructor(type: Parameter, uniqueId: string, bytes: number[]) { super(type, uniqueId, bytes); } @@ -68,4 +71,15 @@ export class FixedSizeByteArrayStatement extends PrimitiveStatement< return sampler.sampleArgument(depth, this.type); } } + + decode(context: ContextBuilder): Decoding[] { + const bytes = web3_utils.bytesToHex(this.value); + + return [ + { + decoded: `const ${context.getOrCreateVariableName(this.type)} = "${bytes}";`, + reference: this, + }, + ]; + } } diff --git a/libraries/search-solidity/lib/testcase/statements/primitive/IntegerStatement.ts b/libraries/search-solidity/lib/testcase/statements/primitive/IntegerStatement.ts index 6d78191..3bf958d 100644 --- a/libraries/search-solidity/lib/testcase/statements/primitive/IntegerStatement.ts +++ b/libraries/search-solidity/lib/testcase/statements/primitive/IntegerStatement.ts @@ -23,6 +23,8 @@ import { PrimitiveStatement } from "./PrimitiveStatement"; import { Int, Parameter, Uint } from "@syntest/analysis-solidity"; import { SoliditySampler } from "../../sampling/SoliditySampler"; import { Statement } from "../Statement"; +import { ContextBuilder } from "../../../testbuilding/ContextBuilder"; +import { Decoding } from "../../../testbuilding/Decoding"; /** * Generic number class @@ -104,4 +106,14 @@ export class IntegerStatement extends PrimitiveStatement< override get value(): BigNumber { return new BigNumber(this._value.toPrecision(0)); } + + decode(context: ContextBuilder): Decoding[] { + const asString = this.value.toString(); + return [ + { + decoded: `const ${context.getOrCreateVariableName(this.type)} = BigInt(${asString});`, + reference: this, + }, + ]; + } } diff --git a/libraries/search-solidity/lib/testcase/statements/primitive/NumericStatement.ts b/libraries/search-solidity/lib/testcase/statements/primitive/NumericStatement.ts index 29c7f4b..6afba7b 100644 --- a/libraries/search-solidity/lib/testcase/statements/primitive/NumericStatement.ts +++ b/libraries/search-solidity/lib/testcase/statements/primitive/NumericStatement.ts @@ -23,6 +23,8 @@ import { PrimitiveStatement } from "./PrimitiveStatement"; import { Fixed, Parameter, Ufixed } from "@syntest/analysis-solidity"; import { SoliditySampler } from "../../sampling/SoliditySampler"; import { Statement } from "../Statement"; +import { ContextBuilder } from "../../../testbuilding/ContextBuilder"; +import { Decoding } from "../../../testbuilding/Decoding"; /** * Generic number class @@ -104,4 +106,14 @@ export class NumericStatement extends PrimitiveStatement< get lower_bound(): BigNumber { return this._lower_bound; } + + decode(context: ContextBuilder): Decoding[] { + const asString = this.value.toString(); + return [ + { + decoded: `const ${context.getOrCreateVariableName(this.type)} = BigNumber(${asString});`, + reference: this, + }, + ]; + } } diff --git a/libraries/search-solidity/lib/testcase/statements/primitive/PrimitiveStatement.ts b/libraries/search-solidity/lib/testcase/statements/primitive/PrimitiveStatement.ts index 8faf361..715f28b 100644 --- a/libraries/search-solidity/lib/testcase/statements/primitive/PrimitiveStatement.ts +++ b/libraries/search-solidity/lib/testcase/statements/primitive/PrimitiveStatement.ts @@ -22,33 +22,26 @@ import { Parameter, Type } from "@syntest/analysis-solidity"; import { SolidityTestCase } from "../../SolidityTestCase"; /** - * @author Dimitri Stallenberg + * PrimitiveStatement */ -export abstract class PrimitiveStatement extends Statement { - get type(): Parameter { - return >this.types[0]; - } - - get varName(): string { - return this.varNames[0]; - } +export abstract class PrimitiveStatement extends Statement { get value(): T { return this._value; } protected _value: T; - constructor(type: Parameter, uniqueId: string, value: T) { - super([type], uniqueId); + constructor(type: Parameter, uniqueId: string, value: T) { + super(type, uniqueId); this._value = value; } - abstract mutate( + abstract override mutate( sampler: EncodingSampler, depth: number ): Statement; - abstract copy(): PrimitiveStatement; + abstract override copy(): PrimitiveStatement; hasChildren(): boolean { return false; @@ -57,4 +50,8 @@ export abstract class PrimitiveStatement extends Statement { getChildren(): Statement[] { return []; } + + override setChild(): void { + throw new Error(`Cannot set child of PrimitiveStatement!`) + } } diff --git a/libraries/search-solidity/lib/testcase/statements/primitive/StringStatement.ts b/libraries/search-solidity/lib/testcase/statements/primitive/StringStatement.ts index 2a98e80..a611af4 100644 --- a/libraries/search-solidity/lib/testcase/statements/primitive/StringStatement.ts +++ b/libraries/search-solidity/lib/testcase/statements/primitive/StringStatement.ts @@ -21,6 +21,8 @@ import { StringType } from "@syntest/analysis-solidity"; import { PrimitiveStatement } from "./PrimitiveStatement"; import { SoliditySampler } from "../../sampling/SoliditySampler"; import { Statement } from "../Statement"; +import { ContextBuilder } from "../../../testbuilding/ContextBuilder"; +import { Decoding } from "../../../testbuilding/Decoding"; /** * String statement @@ -136,4 +138,21 @@ export class StringStatement extends PrimitiveStatement { copy(): StringStatement { return new StringStatement(this.type, this.uniqueId, this.value); } + + override decode(context: ContextBuilder): Decoding[] { + let value = this.value; + + value = value.replaceAll(/\\/g, "\\\\"); + value = value.replaceAll(/\n/g, "\\n"); + value = value.replaceAll(/\r/g, "\\r"); + value = value.replaceAll(/\t/g, "\\t"); + value = value.replaceAll(/"/g, '\\"'); + + return [ + { + decoded: `const ${context.getOrCreateVariableName(this.type)} = "${value}";`, + reference: this, + }, + ]; + } } diff --git a/package-lock.json b/package-lock.json index b997041..71320bb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -55,13 +55,11 @@ } }, "libraries/analysis-solidity": { - "name": "@syntest/analysis-solidity", "version": "0.0.0", "license": "Apache-2.0", "dependencies": { "@solidity-parser/parser": "0.14.5", "@syntest/analysis": "^0.1.0-beta.9", - "@syntest/ast-visitor-solidity": "*", "@syntest/cfg": "^0.3.0-beta.18", "@syntest/logging": "^0.1.0-beta.7", "@syntest/prng": "*", @@ -71,38 +69,7 @@ "node": ">=10.24.0" } }, - "libraries/ast-visitor-solidity": { - "name": "@syntest/ast-visitor-solidity", - "version": "0.0.0", - "license": "Apache-2.0", - "dependencies": { - "@solidity-parser/parser": "0.14.5", - "@syntest/logging": "^0.1.0-beta.7" - }, - "engines": { - "node": ">=10.24.0" - } - }, - "libraries/instrumentation-solidity": { - "name": "@syntest/instrumentation-solidity", - "version": "0.0.0", - "license": "Apache-2.0", - "dependencies": { - "@syntest/analysis": "^0.1.0-beta.9", - "@syntest/analysis-solidity": "*", - "@syntest/ast-visitor-solidity": "*", - "@syntest/cfg": "^0.3.0-beta.18", - "@syntest/logging": "^0.1.0-beta.7", - "@syntest/prng": "*", - "@syntest/search": "^0.4.0-beta.36", - "@syntest/storage": "*" - }, - "engines": { - "node": ">=10.24.0" - } - }, "libraries/search-solidity": { - "name": "@syntest/search-solidity", "version": "0.0.0", "license": "Apache-2.0", "dependencies": { @@ -233,21 +200,21 @@ } }, "node_modules/@babel/core": { - "version": "7.22.17", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.22.17.tgz", - "integrity": "sha512-2EENLmhpwplDux5PSsZnSbnSkB3tZ6QTksgO25xwEL7pIDcNOMhF5v/s6RzwjMZzZzw9Ofc30gHv5ChCC8pifQ==", + "version": "7.22.19", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.22.19.tgz", + "integrity": "sha512-Q8Yj5X4LHVYTbLCKVz0//2D2aDmHF4xzCdEttYvKOnWvErGsa6geHXD6w46x64n5tP69VfeH+IfSrdyH3MLhwA==", "dev": true, "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.22.13", "@babel/generator": "^7.22.15", "@babel/helper-compilation-targets": "^7.22.15", - "@babel/helper-module-transforms": "^7.22.17", + "@babel/helper-module-transforms": "^7.22.19", "@babel/helpers": "^7.22.15", "@babel/parser": "^7.22.16", "@babel/template": "^7.22.15", - "@babel/traverse": "^7.22.17", - "@babel/types": "^7.22.17", + "@babel/traverse": "^7.22.19", + "@babel/types": "^7.22.19", "convert-source-map": "^1.7.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", @@ -385,16 +352,16 @@ } }, "node_modules/@babel/helper-module-transforms": { - "version": "7.22.17", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.22.17.tgz", - "integrity": "sha512-XouDDhQESrLHTpnBtCKExJdyY4gJCdrvH2Pyv8r8kovX2U8G0dRUOT45T9XlbLtuu9CLXP15eusnkprhoPV5iQ==", + "version": "7.22.19", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.22.19.tgz", + "integrity": "sha512-m6h1cJvn+OJ+R3jOHp30faq5xKJ7VbjwDj5RGgHuRlU9hrMeKsGC+JpihkR5w1g7IfseCPPtZ0r7/hB4UKaYlA==", "dev": true, "dependencies": { "@babel/helper-environment-visitor": "^7.22.5", "@babel/helper-module-imports": "^7.22.15", "@babel/helper-simple-access": "^7.22.5", "@babel/helper-split-export-declaration": "^7.22.6", - "@babel/helper-validator-identifier": "^7.22.15" + "@babel/helper-validator-identifier": "^7.22.19" }, "engines": { "node": ">=6.9.0" @@ -437,9 +404,9 @@ } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.15.tgz", - "integrity": "sha512-4E/F9IIEi8WR94324mbDUMo074YTheJmd7eZF5vITTeYchqAi6sYXRLHUVsmkdmY4QjfKTcB2jB7dVP3NaBElQ==", + "version": "7.22.19", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.19.tgz", + "integrity": "sha512-Tinq7ybnEPFFXhlYOYFiSjespWQk0dq2dRNAiMdRTOYQzEGqnnNyrTxPYHP5r6wGjlF1rFgABdDV0g8EwD6Qbg==", "dev": true, "engines": { "node": ">=6.9.0" @@ -580,9 +547,9 @@ } }, "node_modules/@babel/traverse": { - "version": "7.22.17", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.22.17.tgz", - "integrity": "sha512-xK4Uwm0JnAMvxYZxOVecss85WxTEIbTa7bnGyf/+EgCL5Zt3U7htUpEOWv9detPlamGKuRzCqw74xVglDWpPdg==", + "version": "7.22.19", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.22.19.tgz", + "integrity": "sha512-ZCcpVPK64krfdScRbpxF6xA5fz7IOsfMwx1tcACvCzt6JY+0aHkBk7eIU8FRDSZRU5Zei6Z4JfgAxN1bqXGECg==", "dev": true, "dependencies": { "@babel/code-frame": "^7.22.13", @@ -592,7 +559,7 @@ "@babel/helper-hoist-variables": "^7.22.5", "@babel/helper-split-export-declaration": "^7.22.6", "@babel/parser": "^7.22.16", - "@babel/types": "^7.22.17", + "@babel/types": "^7.22.19", "debug": "^4.1.0", "globals": "^11.1.0" }, @@ -610,13 +577,13 @@ } }, "node_modules/@babel/types": { - "version": "7.22.17", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.22.17.tgz", - "integrity": "sha512-YSQPHLFtQNE5xN9tHuZnzu8vPr61wVTBZdfv1meex1NBosa4iT05k/Jw06ddJugi4bk7The/oSwQGFcksmEJQg==", + "version": "7.22.19", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.22.19.tgz", + "integrity": "sha512-P7LAw/LbojPzkgp5oznjE6tQEIWbp4PkkfrZDINTro9zgBRtI324/EYsiSI7lhPbpIQ+DCeR2NNmMWANGGfZsg==", "dev": true, "dependencies": { "@babel/helper-string-parser": "^7.22.5", - "@babel/helper-validator-identifier": "^7.22.15", + "@babel/helper-validator-identifier": "^7.22.19", "to-fast-properties": "^2.0.0" }, "engines": { @@ -2620,6 +2587,21 @@ "node": "^14.15.0 || >=16.0.0" } }, + "node_modules/@lerna/project/node_modules/dot-prop": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-6.0.1.tgz", + "integrity": "sha512-tE7ztYzXHIeyvc7N+hR3oi7FIbf/NIjVP9hmAt3yMXzrQ072/fpjGLx2GxNxGxUl5V73MEqYzioOMoVhGMJ5cA==", + "dev": true, + "dependencies": { + "is-obj": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/@lerna/project/node_modules/glob-parent": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", @@ -2907,15 +2889,6 @@ "uuid": "^8.3.2" } }, - "node_modules/@lerna/temp-write/node_modules/uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", - "dev": true, - "bin": { - "uuid": "dist/bin/uuid" - } - }, "node_modules/@lerna/timer": { "version": "6.4.1", "resolved": "https://registry.npmjs.org/@lerna/timer/-/timer-6.4.1.tgz", @@ -3721,21 +3694,17 @@ "resolved": "libraries/analysis-solidity", "link": true }, - "node_modules/@syntest/ast-visitor-solidity": { - "resolved": "libraries/ast-visitor-solidity", - "link": true - }, "node_modules/@syntest/base-language": { - "version": "0.2.0-beta.68", - "resolved": "https://registry.npmjs.org/@syntest/base-language/-/base-language-0.2.0-beta.68.tgz", - "integrity": "sha512-ZFbdgCdUTbnVfskXFZB4yYrALEAngTbFln+vvpBu02wLVnzYcXQvWgQbOVOIcfsNj4ms1cs1uUASTvs4CumpKw==", + "version": "0.2.0-beta.69", + "resolved": "https://registry.npmjs.org/@syntest/base-language/-/base-language-0.2.0-beta.69.tgz", + "integrity": "sha512-E6ECnJVRLLSHHdbKh61RzUSPITh1Stz76VSXUikwsihHU7CkDL6QRQmNbqE7lzBGdKVXIDBps3tWT0jgO0ZH7g==", "dependencies": { "@syntest/analysis": "^0.1.0-beta.9", "@syntest/cli-graphics": "^0.1.0-beta.3", "@syntest/logging": "^0.1.0-beta.7", "@syntest/metric": "^0.1.0-beta.9", "@syntest/module": "^0.1.0-beta.22", - "@syntest/search": "^0.4.0-beta.54", + "@syntest/search": "^0.4.0-beta.55", "@syntest/storage": "^0.1.0-beta.0", "globby": "11.0.4", "yargs": "^17.7.1" @@ -3806,10 +3775,6 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/@syntest/instrumentation-solidity": { - "resolved": "libraries/instrumentation-solidity", - "link": true - }, "node_modules/@syntest/logging": { "version": "0.1.0-beta.7", "resolved": "https://registry.npmjs.org/@syntest/logging/-/logging-0.1.0-beta.7.tgz", @@ -3864,9 +3829,9 @@ } }, "node_modules/@syntest/search": { - "version": "0.4.0-beta.54", - "resolved": "https://registry.npmjs.org/@syntest/search/-/search-0.4.0-beta.54.tgz", - "integrity": "sha512-wLdniLUoE0SOmw6kQPj40ybe8OTALVkczk8VUD7d+jLB1opCkl/6q00O+3k5KiQkxoBfsNZZ1PVO749r165M9g==", + "version": "0.4.0-beta.55", + "resolved": "https://registry.npmjs.org/@syntest/search/-/search-0.4.0-beta.55.tgz", + "integrity": "sha512-5LIo8BC9JqxjuX84gaJ6p9QCFknhv9fZTFGloFsu2X8Sm9bJzPWrQzrbAAU6LlMm6xAiAroDz8vjByYgOQjwQA==", "dependencies": { "@syntest/analysis": "^0.1.0-beta.9", "@syntest/cfg": "*", @@ -3898,19 +3863,6 @@ "node": ">=16" } }, - "node_modules/@syntest/storage/node_modules/fs-extra": { - "version": "11.1.1", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.1.1.tgz", - "integrity": "sha512-MGIE4HOvQCeUCzmlHs0vXpih4ysz4wg9qiSAu6cd42lVwPbTM1TjV7RusoyQqMmk/95gdQZX72u+YW+c3eEpFQ==", - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, - "engines": { - "node": ">=14.14" - } - }, "node_modules/@szmarczak/http-timer": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-5.0.1.tgz", @@ -4127,9 +4079,9 @@ } }, "node_modules/@types/semver": { - "version": "7.5.1", - "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.1.tgz", - "integrity": "sha512-cJRQXpObxfNKkFAZbJl2yjWtJCqELQIdShsogr1d2MilP8dKD9TE/nEKHkJgUNHdGKCQaf9HbIynuV2csLGVLg==", + "version": "7.5.2", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.2.tgz", + "integrity": "sha512-7aqorHYgdNO4DM36stTiGO3DvKoex9TQRwsJU6vMaFGyqpBA1MNZkz+PG3gaNUPpTAOYhT1WR7M1JyA3fbS9Cw==", "dev": true }, "node_modules/@types/triple-beam": { @@ -5991,18 +5943,6 @@ "dot-prop": "^5.1.0" } }, - "node_modules/compare-func/node_modules/dot-prop": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.3.0.tgz", - "integrity": "sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==", - "dev": true, - "dependencies": { - "is-obj": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -6353,9 +6293,10 @@ "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" }, "node_modules/core-util-is": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==" + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "dev": true }, "node_modules/cors": { "version": "2.8.5", @@ -6820,18 +6761,15 @@ "integrity": "sha512-6QvTW9mrGeIegrFXdtQi9pk7O/nSK6lSdXW2eqUspN5LWD7UTji2Fqw5V2YLjBpHEoU9Xl/eUWNpDeZvoyOv2w==" }, "node_modules/dot-prop": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-6.0.1.tgz", - "integrity": "sha512-tE7ztYzXHIeyvc7N+hR3oi7FIbf/NIjVP9hmAt3yMXzrQ072/fpjGLx2GxNxGxUl5V73MEqYzioOMoVhGMJ5cA==", + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.3.0.tgz", + "integrity": "sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==", "dev": true, "dependencies": { "is-obj": "^2.0.0" }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=8" } }, "node_modules/dotenv": { @@ -6885,9 +6823,9 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.4.520", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.520.tgz", - "integrity": "sha512-Frfus2VpYADsrh1lB3v/ft/WVFlVzOIm+Q0p7U7VqHI6qr7NWHYKe+Wif3W50n7JAFoBsWVsoU0+qDks6WQ60g==", + "version": "1.4.522", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.522.tgz", + "integrity": "sha512-KGKjcafTpOxda0kqwQ72M0tDmX6RsGhUJTy0Hr7slt0+CgHh9Oex8JdjY9Og68dUkTLUlBOJC0A5W5Mw3QSGCg==", "dev": true }, "node_modules/elliptic": { @@ -7013,17 +6951,17 @@ } }, "node_modules/es-abstract": { - "version": "1.22.1", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.22.1.tgz", - "integrity": "sha512-ioRRcXMO6OFyRpyzV3kE1IIBd4WG5/kltnzdxSCqoP8CMGs/Li+M1uF5o7lOkZVFjDs+NLesthnF66Pg/0q0Lw==", + "version": "1.22.2", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.22.2.tgz", + "integrity": "sha512-YoxfFcDmhjOgWPWsV13+2RNjq1F6UQnfs+8TftwNqtzlmFzEXvlUwdrNrYeaizfjQzRMxkZ6ElWMOJIFKdVqwA==", "dependencies": { "array-buffer-byte-length": "^1.0.0", - "arraybuffer.prototype.slice": "^1.0.1", + "arraybuffer.prototype.slice": "^1.0.2", "available-typed-arrays": "^1.0.5", "call-bind": "^1.0.2", "es-set-tostringtag": "^2.0.1", "es-to-primitive": "^1.2.1", - "function.prototype.name": "^1.1.5", + "function.prototype.name": "^1.1.6", "get-intrinsic": "^1.2.1", "get-symbol-description": "^1.0.0", "globalthis": "^1.0.3", @@ -7039,23 +6977,23 @@ "is-regex": "^1.1.4", "is-shared-array-buffer": "^1.0.2", "is-string": "^1.0.7", - "is-typed-array": "^1.1.10", + "is-typed-array": "^1.1.12", "is-weakref": "^1.0.2", "object-inspect": "^1.12.3", "object-keys": "^1.1.1", "object.assign": "^4.1.4", - "regexp.prototype.flags": "^1.5.0", - "safe-array-concat": "^1.0.0", + "regexp.prototype.flags": "^1.5.1", + "safe-array-concat": "^1.0.1", "safe-regex-test": "^1.0.0", - "string.prototype.trim": "^1.2.7", - "string.prototype.trimend": "^1.0.6", - "string.prototype.trimstart": "^1.0.6", + "string.prototype.trim": "^1.2.8", + "string.prototype.trimend": "^1.0.7", + "string.prototype.trimstart": "^1.0.7", "typed-array-buffer": "^1.0.0", "typed-array-byte-length": "^1.0.0", "typed-array-byte-offset": "^1.0.0", "typed-array-length": "^1.0.4", "unbox-primitive": "^1.0.2", - "which-typed-array": "^1.1.10" + "which-typed-array": "^1.1.11" }, "engines": { "node": ">= 0.4" @@ -8329,10 +8267,9 @@ "dev": true }, "node_modules/fs-extra": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.1.0.tgz", - "integrity": "sha512-0rcTq621PD5jM/e0a3EJoGC/1TC5ZBCERW82LQuwfGnCa1V8w7dpYH1yNu+SLb6E5dkeCBzKEyLGlFrnr+dUyw==", - "dev": true, + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.1.1.tgz", + "integrity": "sha512-MGIE4HOvQCeUCzmlHs0vXpih4ysz4wg9qiSAu6cd42lVwPbTM1TjV7RusoyQqMmk/95gdQZX72u+YW+c3eEpFQ==", "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", @@ -10144,15 +10081,6 @@ "node": ">=8" } }, - "node_modules/istanbul-lib-processinfo/node_modules/uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", - "dev": true, - "bin": { - "uuid": "dist/bin/uuid" - } - }, "node_modules/istanbul-lib-report": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", @@ -11517,6 +11445,15 @@ "node": ">= 0.6" } }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, "node_modules/mimic-response": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", @@ -12991,15 +12928,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/onetime/node_modules/mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "dev": true, - "engines": { - "node": ">=6" - } - }, "node_modules/open": { "version": "8.4.2", "resolved": "https://registry.npmjs.org/open/-/open-8.4.2.tgz", @@ -14391,6 +14319,15 @@ "node": ">=0.6" } }, + "node_modules/request/node_modules/uuid": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", + "deprecated": "Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.", + "bin": { + "uuid": "bin/uuid" + } + }, "node_modules/require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", @@ -14413,9 +14350,9 @@ "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==" }, "node_modules/resolve": { - "version": "1.22.4", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.4.tgz", - "integrity": "sha512-PXNdCiPqDqeUou+w1C2eTQbNfxKSuMxqTCuvlmmMsk1NWHL5fRrhY6Pl0qEYYc6+QqGClco1Qj8XnjPego4wfg==", + "version": "1.22.5", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.5.tgz", + "integrity": "sha512-qWhv7PF1V95QPvRoUGHxOtnAlEvlXBylMZcjUR9pAumMmveFtcHJRXGIr+TkjfNJVQypqv2qcDiiars2y1PsSg==", "dev": true, "dependencies": { "is-core-module": "^2.13.0", @@ -16988,12 +16925,12 @@ } }, "node_modules/uuid": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", - "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", - "deprecated": "Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.", + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "dev": true, "bin": { - "uuid": "bin/uuid" + "uuid": "dist/bin/uuid" } }, "node_modules/v8-compile-cache": { @@ -17056,6 +16993,11 @@ "extsprintf": "^1.2.0" } }, + "node_modules/verror/node_modules/core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==" + }, "node_modules/walk-up-path": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/walk-up-path/-/walk-up-path-1.0.0.tgz", @@ -18013,7 +17955,6 @@ } }, "tools/solidity": { - "name": "@syntest/solidity", "version": "0.2.1", "license": "Apache-2.0", "dependencies": { @@ -18021,7 +17962,6 @@ "@syntest/analysis-solidity": "*", "@syntest/base-language": "^0.2.0-beta.39", "@syntest/cli-graphics": "^0.1.0-beta.3", - "@syntest/instrumentation-solidity": "*", "@syntest/logging": "^0.1.0-beta.7", "@syntest/metric": "^0.1.0-beta.6", "@syntest/module": "^0.1.0-beta.18", diff --git a/tools/solidity/lib/SolidityLauncher.ts b/tools/solidity/lib/SolidityLauncher.ts index ce34886..e964ce5 100644 --- a/tools/solidity/lib/SolidityLauncher.ts +++ b/tools/solidity/lib/SolidityLauncher.ts @@ -61,8 +61,7 @@ import { } from "@syntest/search-solidity"; import * as path from "node:path"; -import TruffleConfig = require("@truffle/config"); -import API = require("./api"); + import { createTruffleConfig } from "./util/fileSystem"; @@ -78,6 +77,12 @@ import { TargetFactory, Target, } from "@syntest/analysis-solidity"; + +// eslint-disable-next-line @typescript-eslint/no-var-requires, unicorn/prefer-module +const TruffleConfig = require("@truffle/config"); +// eslint-disable-next-line @typescript-eslint/no-var-requires, unicorn/prefer-module +const API = require("./api"); +// eslint-disable-next-line @typescript-eslint/no-var-requires, unicorn/prefer-module const Web3 = require("web3"); export type SolidityArguments = ArgumentsObject & TestCommandOptions; @@ -97,9 +102,12 @@ export class SolidityLauncher extends Launcher { private decoder: SolidityDecoder; private runner: SolidityRunner; - private api; - private config; - private truffle; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + private api: any; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + private config: any; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + private truffle: any; constructor( arguments_: SolidityArguments, @@ -116,6 +124,8 @@ export class SolidityLauncher extends Launcher { userInterface ); SolidityLauncher.LOGGER = getLogger("SolidityLauncher"); + + this.dependencyMap = new Map() } async initialize(): Promise { @@ -305,7 +315,7 @@ export class SolidityLauncher extends Launcher { const startTargetSelection = Date.now(); const targetSelector = new TargetSelector(this.rootContext); - this.targets = targetSelector.loadTargets( + this.targets = targetSelector.loadTargets( this.arguments_.targetInclude, this.arguments_.targetExclude ); @@ -458,13 +468,17 @@ export class SolidityLauncher extends Launcher { `${timeInMs}` ); + for (const target of this.targets) { + this.dependencyMap.set(target.name, this.rootContext.getDependencies(target.path)) + } + this.decoder = new SolidityDecoder( - this.arguments_.targetRootDirectory, path.join( this.arguments_.tempSyntestDirectory, this.arguments_.fid, this.arguments_.logDirectory - ) + ), + this.dependencyMap ); this.runner = new SolidityRunner( @@ -510,10 +524,7 @@ export class SolidityLauncher extends Launcher { this.storageManager, this.decoder, this.runner, - this.arguments_.logDirectory, - this.api, - this.truffle, - this.config + this.arguments_.logDirectory ); const reducedArchive = suiteBuilder.reduceArchive(this.archive); @@ -712,7 +723,7 @@ export class SolidityLauncher extends Launcher { this.arguments_.stringAlphabet ); - const rootTargets = currentSubject.getPossibleActions(); + const rootTargets = currentSubject.getActionableTargets(); if (rootTargets.length === 0) { SolidityLauncher.LOGGER.info( @@ -739,7 +750,8 @@ export class SolidityLauncher extends Launcher { this.arguments_.stringAlphabet, this.arguments_.stringMaxLength, this.arguments_.deltaMutationProbability, - this.arguments_.exploreIllegalValues + this.arguments_.exploreIllegalValues, + (this.arguments_).numericDecimals ); sampler.rootContext = rootContext; diff --git a/tools/solidity/lib/SolidityModule.ts b/tools/solidity/lib/SolidityModule.ts index 360eb63..94af0fe 100644 --- a/tools/solidity/lib/SolidityModule.ts +++ b/tools/solidity/lib/SolidityModule.ts @@ -21,6 +21,9 @@ import { MetricManager } from "@syntest/metric"; import { ModuleManager, Module, Tool } from "@syntest/module"; import { StorageManager } from "@syntest/storage"; import yargs = require("yargs"); +import { getTestCommand } from "./commands/test"; +import { TreeCrossoverPlugin } from "./plugins/crossover/TreeCrossoverPlugin"; +import { RandomSamplerPlugin } from "./plugins/sampler/RandomSamplerPlugin"; export default class SolidityModule extends TestingToolModule { constructor() { diff --git a/tools/solidity/lib/plugins/crossover/TreeCrossoverPlugin.ts b/tools/solidity/lib/plugins/crossover/TreeCrossoverPlugin.ts new file mode 100644 index 0000000..50de35b --- /dev/null +++ b/tools/solidity/lib/plugins/crossover/TreeCrossoverPlugin.ts @@ -0,0 +1,41 @@ +/* + * Copyright 2020-2023 Delft University of Technology and SynTest contributors + * + * This file is part of SynTest Framework - SynTest Core. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { CrossoverOptions, CrossoverPlugin } from "@syntest/base-language"; +import { SolidityTestCase, TreeCrossover } from "@syntest/search-solidity"; + +/** + * Plugin for Tree Crossover + * + * @author Dimitri Stallenberg + */ +export class TreeCrossoverPlugin extends CrossoverPlugin { + constructor() { + super("solidity-tree", "A Solidity tree crossover plugin"); + } + + createCrossoverOperator(options: CrossoverOptions): TreeCrossover { + return new TreeCrossover( + options.crossoverEncodingProbability, + options.crossoverStatementProbability + ); + } + + override getOptions() { + return new Map(); + } +} diff --git a/tools/solidity/lib/plugins/sampler/RandomSamplerPlugin.ts b/tools/solidity/lib/plugins/sampler/RandomSamplerPlugin.ts new file mode 100644 index 0000000..f952b9b --- /dev/null +++ b/tools/solidity/lib/plugins/sampler/RandomSamplerPlugin.ts @@ -0,0 +1,59 @@ +/* + * Copyright 2020-2023 Delft University of Technology and SynTest contributors + * + * This file is part of SynTest Framework - SynTest Core. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { SamplerOptions, SamplerPlugin } from "@syntest/base-language"; +import { + SolidityRandomSampler, + SoliditySubject, + SolidityTestCase, +} from "@syntest/search-solidity"; +import { EncodingSampler } from "@syntest/search"; +import { SolidityArguments } from "../../SolidityLauncher"; + +/** + * Plugin for RandomSampler + * + * @author Dimitri Stallenberg + */ +export class RandomSamplerPlugin extends SamplerPlugin { + constructor() { + super("solidity-random", "A Solidity random sampler plugin"); + } + + createSamplerOperator( + options: SamplerOptions + ): EncodingSampler { + return new SolidityRandomSampler( + options.subject as unknown as SoliditySubject, + undefined, // TODO incorrect constant pool should be part of sampler options + ((this.args)).constantPool, + ((this.args)).constantPoolProbability, + ((this.args)).statementPool, + ((this.args)).statementPoolProbability, + ((this.args)).maxActionStatements, + ((this.args)).stringAlphabet, + ((this.args)).stringMaxLength, + ((this.args)).deltaMutationProbability, + ((this.args)).exploreIllegalValues, + ((this.args)).numericDecimals + ); + } + + override getOptions() { + return new Map(); + } +} diff --git a/tools/solidity/lib/util/network.ts b/tools/solidity/lib/util/network.ts index 4010d19..d285d89 100644 --- a/tools/solidity/lib/util/network.ts +++ b/tools/solidity/lib/util/network.ts @@ -15,9 +15,8 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { getUserInterface } from "@syntest/search"; -import * as TruffleProvider from "@truffle/provider"; -import TruffleConfig = require("@truffle/config"); +// eslint-disable-next-line unicorn/prefer-module, @typescript-eslint/no-var-requires +const TruffleProvider = require("@truffle/provider"); /** * Configures the network. Runs before the server is launched. @@ -30,8 +29,8 @@ import TruffleConfig = require("@truffle/config"); * @param {TruffleConfig} config * @param {SolidityCoverage} api */ -// eslint-disable-next-line -export function setNetwork(config: TruffleConfig, api: any): void { +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export function setNetwork(config: any, api: any): void { // --network if (config.network) { const network = config.networks[config.network]; @@ -42,7 +41,7 @@ export function setNetwork(config: TruffleConfig, api: any): void { } // Check network id - if (isNaN(Number.parseInt(network.network_id))) { + if (Number.isNaN(Number.parseInt(network.network_id))) { network.network_id = "*"; } else { // Warn: non-matching provider options id and network id @@ -50,7 +49,7 @@ export function setNetwork(config: TruffleConfig, api: any): void { api.providerOptions.network_id && api.providerOptions.network_id !== Number.parseInt(network.network_id) ) { - getUserInterface().info( + console.info( "id-clash " + [Number.parseInt(network.network_id)] ); } @@ -61,13 +60,13 @@ export function setNetwork(config: TruffleConfig, api: any): void { // Check port: use solcoverjs || default if undefined if (!network.port) { - getUserInterface().info("no-port " + [api.port]); + console.info("no-port " + [api.port]); network.port = api.port; } // Warn: port conflicts if (api.port !== api.defaultPort && api.port !== network.port) { - getUserInterface().info("port-clash " + [network.port]); + console.info("port-clash " + [network.port]); } // Prefer network port if defined; @@ -95,12 +94,12 @@ export function setNetwork(config: TruffleConfig, api: any): void { // Truffle complains that these outer keys *are not* set when running plugin fn directly. // But throws saying they *cannot* be manually set when running as truffle command. -// eslint-disable-next-line export function setOuterConfigKeys( - config: TruffleConfig, - // eslint-disable-next-line + // eslint-disable-next-line @typescript-eslint/no-explicit-any + config: any, + // eslint-disable-next-line @typescript-eslint/no-explicit-any api: any, - // eslint-disable-next-line + // eslint-disable-next-line @typescript-eslint/no-explicit-any id: any ): void { try { @@ -120,8 +119,8 @@ export function setOuterConfigKeys( * @param {TruffleConfig} config * @param {Array} accounts */ -// eslint-disable-next-line -export function setNetworkFrom(config: TruffleConfig, accounts: any[]): void { +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export function setNetworkFrom(config: any, accounts: any[]): void { if (!config.networks[config.network].from) { config.networks[config.network].from = accounts[0]; } diff --git a/tools/solidity/test/example.test.ts b/tools/solidity/test/example.test.ts index e0fdf1d..9f5e206 100644 --- a/tools/solidity/test/example.test.ts +++ b/tools/solidity/test/example.test.ts @@ -17,7 +17,7 @@ */ import * as chai from "chai"; -import { SolidityLauncher } from "../lib/SolidityLauncher"; +import { TreeCrossoverPlugin } from "../lib/plugins/crossover/TreeCrossoverPlugin"; const expect = chai.expect; @@ -26,7 +26,7 @@ const expect = chai.expect; */ describe("example test", () => { it("test", () => { - new SolidityLauncher(); + new TreeCrossoverPlugin(); expect(true); }); });