From 58a9ef9ecc2a0a8ef475116d0465517e2045ac25 Mon Sep 17 00:00:00 2001 From: Dimitri Stallenberg Date: Thu, 5 Oct 2023 13:00:39 +0200 Subject: [PATCH 1/4] fix: finalize experiment --- libraries/analysis-javascript/index.ts | 4 + .../analysis-javascript/lib/RootContext.ts | 30 ++- .../lib/dynamic/action/Action.ts | 27 ++ .../lib/dynamic/action/ActionFactory.ts | 97 +++++++ .../lib/dynamic/action/Executor.ts | 246 ++++++++++++++++++ tools/javascript/lib/JavaScriptLauncher.ts | 14 +- 6 files changed, 415 insertions(+), 3 deletions(-) create mode 100644 libraries/analysis-javascript/lib/dynamic/action/Action.ts create mode 100644 libraries/analysis-javascript/lib/dynamic/action/ActionFactory.ts create mode 100644 libraries/analysis-javascript/lib/dynamic/action/Executor.ts diff --git a/libraries/analysis-javascript/index.ts b/libraries/analysis-javascript/index.ts index 6194b63fe..1be799ab5 100644 --- a/libraries/analysis-javascript/index.ts +++ b/libraries/analysis-javascript/index.ts @@ -29,6 +29,10 @@ export * from "./lib/constant/ConstantVisitor"; export * from "./lib/dependency/DependencyFactory"; export * from "./lib/dependency/DependencyVisitor"; +export * from "./lib/dynamic/action/Action"; +export * from "./lib/dynamic/action/ActionFactory"; +export * from "./lib/dynamic/action/Executor"; + export * from "./lib/target/export/Export"; export * from "./lib/target/export/ExportDefaultDeclaration"; export * from "./lib/target/export/ExportFactory"; diff --git a/libraries/analysis-javascript/lib/RootContext.ts b/libraries/analysis-javascript/lib/RootContext.ts index 289c0971d..0eab1f871 100644 --- a/libraries/analysis-javascript/lib/RootContext.ts +++ b/libraries/analysis-javascript/lib/RootContext.ts @@ -29,6 +29,8 @@ import { ConstantPool } from "./constant/ConstantPool"; import { ConstantPoolFactory } from "./constant/ConstantPoolFactory"; import { ConstantPoolManager } from "./constant/ConstantPoolManager"; import { DependencyFactory } from "./dependency/DependencyFactory"; +import { Action } from "./dynamic/action/Action"; +import { ActionFactory } from "./dynamic/action/ActionFactory"; import { Events } from "./Events"; import { Export } from "./target/export/Export"; import { ExportFactory } from "./target/export/ExportFactory"; @@ -51,6 +53,8 @@ export class RootContext extends CoreRootContext { protected _constantPoolFactory: ConstantPoolFactory; + protected _actionFactory: ActionFactory; + protected _targetFiles: Set; protected _analysisFiles: Set; @@ -66,6 +70,7 @@ export class RootContext extends CoreRootContext { // Mapping: filepath -> target name -> Exports protected _exportMap: Map; + protected _actionMap: Map; constructor( rootPath: string, @@ -78,7 +83,8 @@ export class RootContext extends CoreRootContext { exportFactory: ExportFactory, typeExtractor: TypeExtractor, typeResolver: TypeModelFactory, - constantPoolFactory: ConstantPoolFactory + constantPoolFactory: ConstantPoolFactory, + actionFactory: ActionFactory ) { super( rootPath, @@ -95,6 +101,7 @@ export class RootContext extends CoreRootContext { this._typeExtractor = typeExtractor; this._typeResolver = typeResolver; this._constantPoolFactory = constantPoolFactory; + this._actionFactory = actionFactory } get rootPath(): string { @@ -133,6 +140,11 @@ export class RootContext extends CoreRootContext { return this._sources.get(absoluteTargetPath); } + async getActions(filePath: string) { + const factory = new ActionFactory(1000) + return await factory.extract(filePath, this.getSource(filePath)) + } + getExports(filePath: string): Export[] { const absolutePath = this.resolvePath(filePath); @@ -159,6 +171,18 @@ export class RootContext extends CoreRootContext { return this._exportMap.get(absolutePath); } + async getAllActions() { + if (!this._actionMap) { + this._actionMap = new Map() + + for (const filepath of this._analysisFiles) { + this._actionMap.set(filepath, await this.getActions(filepath)) + } + } + + return this._actionMap + } + getAllExports(): Map { if (!this._exportMap) { this._exportMap = new Map(); @@ -354,4 +378,8 @@ export class RootContext extends CoreRootContext { RootContext.LOGGER.info("Extracting constants done"); return constantPoolManager; } + + exit() { + this._actionFactory.exit() + } } diff --git a/libraries/analysis-javascript/lib/dynamic/action/Action.ts b/libraries/analysis-javascript/lib/dynamic/action/Action.ts new file mode 100644 index 000000000..c2fed1df3 --- /dev/null +++ b/libraries/analysis-javascript/lib/dynamic/action/Action.ts @@ -0,0 +1,27 @@ +/* + * 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. + */ + +export type Action = { + id: string + type: ActionType + children: { + [key: string]: Action + } +} + +export type ActionType = 'function' | 'method' | 'class' | 'object' | 'constructor' | 'setter' | 'getter' \ No newline at end of file diff --git a/libraries/analysis-javascript/lib/dynamic/action/ActionFactory.ts b/libraries/analysis-javascript/lib/dynamic/action/ActionFactory.ts new file mode 100644 index 000000000..71cc75cb2 --- /dev/null +++ b/libraries/analysis-javascript/lib/dynamic/action/ActionFactory.ts @@ -0,0 +1,97 @@ +/* + * 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 { ChildProcess, fork } from "node:child_process"; +import * as path from "node:path"; + +import { getLogger, Logger } from "@syntest/logging"; + +import { Action } from "./Action"; +import { ExecuteMessage, ResultMessage } from "./Executor"; + +export class ActionFactory { + protected static LOGGER: Logger; + + private executionTimeout: number + private _process: ChildProcess; + + constructor(executionTimeout: number) { + ActionFactory.LOGGER = getLogger(ActionFactory.name); + + this.executionTimeout = executionTimeout + // eslint-disable-next-line unicorn/prefer-module + this._process = fork(path.join(__dirname, "Executor.js")); + console.log('created') + } + + exit() { + if (this._process) { + this._process.kill() + } + } + + async extract(filePath: string, source: string) { + // try catch maybe? + return await this._extract(filePath, source) + } + + private async _extract(filePath: string, source: string): Promise { + if (!this._process.connected || this._process.killed) { + // eslint-disable-next-line unicorn/prefer-module + this._process = fork(path.join(__dirname, "Executor.js")); + } + const childProcess = this._process; + + return await new Promise((resolve, reject) => { + const timeout = setTimeout(() => { + ActionFactory.LOGGER.warn( + `Execution timeout reached killing process, timeout: ${this.executionTimeout}` + ); + childProcess.removeAllListeners(); + childProcess.kill(); + reject("timeout"); + }, this.executionTimeout); + + childProcess.on("message", (message: ResultMessage) => { + if (typeof message !== "object") { + return reject( + new TypeError("Invalid data received from child process") + ); + } + + if (message.message === "result") { + childProcess.removeAllListeners(); + clearTimeout(timeout); + return resolve(message.actions); + } + }); + + childProcess.on("error", (error) => { + reject(error); + }); + + const executeMessage: ExecuteMessage = { + message: 'execute', + filePath: filePath, + source: source + } + + childProcess.send(executeMessage); + }); + } +} \ No newline at end of file diff --git a/libraries/analysis-javascript/lib/dynamic/action/Executor.ts b/libraries/analysis-javascript/lib/dynamic/action/Executor.ts new file mode 100644 index 000000000..076d08a22 --- /dev/null +++ b/libraries/analysis-javascript/lib/dynamic/action/Executor.ts @@ -0,0 +1,246 @@ +/* + * 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 { Action, ActionType } from "./Action"; + + +export type ExecuteMessage = { + message: 'execute', + filePath: string + source: string +} + +export type ResultMessage = { + message: 'result', + actions: Action[] +} + +// eslint-disable-next-line @typescript-eslint/no-misused-promises +process.on("message", async (data: ExecuteMessage) => { + if (typeof data !== "object") { + throw new TypeError("Invalid data received from child process"); + } + if (data.message === "execute") { + await gatherIntel(data.filePath, data.source); + } + }); + + function getLineNumber(text: string, index: number) { + let line = 1; + let column = 1; + for (let index_ = 0; index_ < index; index_++) { + column++ + if (text[index_] === '\n') { + column = 1 + line++; + } + if (text[index_] === '\r') { + // A line feed after a carriage return counts as part of the same newline + if (text[index_ + 1] === '\n') { + index_++; + } + column = 1 + line++; + } + } + return {line, column}; + } + +function isClass(object: any) { + if (object == undefined || object === undefined) { return false; } + + const isCtorClass = object.constructor + && object.constructor.toString().slice(0, 5) === 'class' + // const isNativeCtorClass= object.constructor && + // object.constructor.name != "Function" && + // object.constructor.name in global; + // console.log(isCtorClass, isNativeCtorClass) + if (object.prototype === undefined) { + return isCtorClass// || isNativeCtorClass + } + const isPrototypeCtorClass = object.prototype.constructor + && object.prototype.constructor.toString + && object.prototype.constructor.toString().slice(0, 5) === 'class' + + const isNativePrototypeCtorClass = object.prototype.constructor.name in global && ( + (global)[object.prototype.constructor.name] == object.constructor || + (global)[object.prototype.constructor.name] == object + ); + + const hasPrototypes = object.prototype && Object.keys(object.prototype).length > 0 + return isCtorClass || isPrototypeCtorClass || isNativePrototypeCtorClass || hasPrototypes +} + +// function getAllFuncs(toCheck: any) { +// const properties: string[] = []; +// let object = toCheck; +// do { +// properties.push(...Object.getOwnPropertyNames(object)); +// } while (((object = Object.getPrototypeOf(object)) && object != Object.prototype)); + +// return properties.sort().filter((e, index, array) => { +// if (Object.prototype.hasOwnProperty(e)) { +// return false +// } +// if (e!=array[index+1] && typeof toCheck[e] == 'function') return true; +// return false +// }); +// } + +function getAllMethodNames(object: any) { + const methods = new Set(); + + if (!(object instanceof Object)) { + return methods + } + + do { + const keys = Reflect.ownKeys(object) + for (const k of keys) methods.add(k); + } while (((object = Reflect.getPrototypeOf(object)) && object != Object.prototype)) + return methods; +} + +async function gatherIntel(filePath: string, source: string) { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + const import_ = await import(filePath) + + let rootAction: Action + + // queue of [parent, key, child] + const queue: [Action, string, unknown][] = [[undefined, undefined, import_]] + + while (queue.length > 0) { + const [parent, key, child] = queue.shift() + + if (!(child instanceof Object)) { + continue + } + + console.log(child) + const text: string = child.toString() + const startIndex = source.indexOf(text) + + let id: string + let type: ActionType + if (startIndex === -1) { + // its actually an object or class + console.log('no index') + console.log(child.constructor) + console.log() + id = isClass(child) ? 'class' : 'object' + type = isClass(child) ? 'class' : 'object' + } else { + const endIndex = startIndex + text.length + const {line: startLine, column: startColumn} = getLineNumber(source, startIndex) + const {line: endLine, column: endColumn} = getLineNumber(source, endIndex) + id = `${filePath}:${startLine}:${startColumn}:::${endLine}:${endColumn}:::${startIndex}:${endIndex}` + type = isClass(child) ? 'class' : 'function' + } + + const childAction: Action = { + id: id, + type: type, + children: {} + } + + if (!rootAction) { + rootAction = childAction + } + + if (parent) { + parent.children[key] = childAction + } + + console.log('== keys') + console.log(Object.keys(child)) + for (const key of Object.keys(child)) { + if (key === 'prototype') { + continue + } + queue.push([childAction, key, (child)[key]]) + } + + // console.log('== prototypes') + // for (const key in (child).prototype) { + // console.log(key) + // queue.push([childAction, key, (child).prototype[key]]) + // } + console.log('== properties') + + const properties: Set = getAllMethodNames((child).prototype) + // const defaultProperties = Object.getOwnPropertyNames(Object.getPrototypeOf(Object)) + console.log(properties) + for (const key of properties) { + if ((child).prototype[key] === child) { + continue + } + queue.push([childAction, key.toString(), (child).prototype[key]]) + } + console.log('== end') + // for (const key of Object.keys(child)) { + // queue.push([childAction, (child)[key]]) + // } + + // if (isClass(child)) { + // // Object. + // for (const key of Object.getOwnPropertyNames((child).prototype)) { + // queue.push([childAction, (child).prototype[key]]) + // } + + // } + } + + // console.log(import_) + // for (const x of Object.keys(import_)) { + // if (typeof import_[x] !== 'function') { + // return + // } + // let type: ActionType + // console.log(x) + // console.log(import_[x]) + // console.log(Object.keys(import_[x].prototype)) + // if (import_[x].constructor) { + // console.log(import_[x].constructor) + // type = 'class' + // } else { + // type = 'function' + // } + + // const text: string = import_[x].toString() + // const startIndex = source.indexOf(text) + // const endIndex = startIndex + text.length + // const {line: startLine, column: startColumn} = getLineNumber(source, startIndex) + // const {line: endLine, column: endColumn} = getLineNumber(source, endIndex) + // const id = `${filePath}:${startLine}:${startColumn}:::${endLine}:${endColumn}:::${startIndex}:${endIndex}` + + // actions.push({ + // id: id, + // type: type, + // children: [] + // }) + // } + console.log(rootAction) + console.log(JSON.stringify(rootAction, undefined, 2)) + + const resultMessage: ResultMessage = { + message: 'result', + actions: [rootAction] + } + process.send(resultMessage) +} diff --git a/tools/javascript/lib/JavaScriptLauncher.ts b/tools/javascript/lib/JavaScriptLauncher.ts index 2c7b343a7..29fbbe9b2 100644 --- a/tools/javascript/lib/JavaScriptLauncher.ts +++ b/tools/javascript/lib/JavaScriptLauncher.ts @@ -31,6 +31,7 @@ import { TargetFactory, TypeExtractor, TypeModelFactory, + ActionFactory } from "@syntest/analysis-javascript"; import { ArgumentsObject, @@ -201,6 +202,8 @@ export class JavaScriptLauncher extends Launcher { this.arguments_.analysisExclude ); + const actionFactory = new ActionFactory((this.arguments_).executionTimeout) + this.rootContext = new RootContext( this.arguments_.targetRootDirectory, targetFiles, @@ -212,7 +215,8 @@ export class JavaScriptLauncher extends Launcher { exportFactory, typeExtractor, typeResolver, - constantPoolFactory + constantPoolFactory, + actionFactory ); this.userInterface.printHeader("GENERAL INFO"); @@ -403,7 +407,7 @@ export class JavaScriptLauncher extends Launcher { this.userInterface.printTable("DIRECTORY SETTINGS", directorySettings); - JavaScriptLauncher.LOGGER.info("Instrumenting targets"); + JavaScriptLauncher.LOGGER.info("Instrumenting target files"); const startInstrumentation = Date.now(); const instrumenter = new Instrumenter(); await instrumenter.instrumentAll( @@ -418,6 +422,9 @@ export class JavaScriptLauncher extends Launcher { `${timeInMs}` ); + JavaScriptLauncher.LOGGER.info("Gathering actions"); + await this.rootContext.getAllActions() + const startTypeResolving = Date.now(); JavaScriptLauncher.LOGGER.info("Extracting types"); this.rootContext.getAllElements(); @@ -917,6 +924,9 @@ export class JavaScriptLauncher extends Launcher { if (this.runner && this.runner.process) { this.runner.process.kill(); } + if (this.rootContext) { + this.rootContext.exit() + } // TODO should be cleanup step in tool // Finish JavaScriptLauncher.LOGGER.info("Deleting temporary directories"); From 145bbd9febc4a3b0fec1673f18a440c88213dc21 Mon Sep 17 00:00:00 2001 From: Dimitri Stallenberg Date: Thu, 5 Oct 2023 13:49:06 +0200 Subject: [PATCH 2/4] chore: try something with inspector --- .../lib/dynamic/action/Executor.ts | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/libraries/analysis-javascript/lib/dynamic/action/Executor.ts b/libraries/analysis-javascript/lib/dynamic/action/Executor.ts index 076d08a22..d084614d7 100644 --- a/libraries/analysis-javascript/lib/dynamic/action/Executor.ts +++ b/libraries/analysis-javascript/lib/dynamic/action/Executor.ts @@ -16,8 +16,9 @@ * limitations under the License. */ -import { Action, ActionType } from "./Action"; +import { Session } from "node:inspector"; +import { Action, ActionType } from "./Action"; export type ExecuteMessage = { message: 'execute', @@ -122,6 +123,17 @@ async function gatherIntel(filePath: string, source: string) { let rootAction: Action + const session = new Session() + session.connect() + let objectId + session.post('Runtime.evaluate', { expression: source }, (error, {result}) => { + console.log(result) + objectId = result.objectId + }) + session.post('Runtime.getProperties', { objectId }, (error, {internalProperties}) => { + console.log(internalProperties) + }) + console.log('aaaa') // queue of [parent, key, child] const queue: [Action, string, unknown][] = [[undefined, undefined, import_]] From 01ffc54d5ce60fbb04a6a50a7b025786a955da07 Mon Sep 17 00:00:00 2001 From: Dimitri Stallenberg Date: Thu, 5 Oct 2023 13:49:27 +0200 Subject: [PATCH 3/4] chore: try something with inspector --- .../analysis-javascript/lib/RootContext.ts | 16 +- .../lib/dynamic/action/Action.ts | 21 +- .../lib/dynamic/action/ActionFactory.ts | 124 +++--- .../lib/dynamic/action/Executor.ts | 394 ++++++++++-------- tools/javascript/lib/JavaScriptLauncher.ts | 10 +- 5 files changed, 301 insertions(+), 264 deletions(-) diff --git a/libraries/analysis-javascript/lib/RootContext.ts b/libraries/analysis-javascript/lib/RootContext.ts index 0eab1f871..38c5b998c 100644 --- a/libraries/analysis-javascript/lib/RootContext.ts +++ b/libraries/analysis-javascript/lib/RootContext.ts @@ -101,7 +101,7 @@ export class RootContext extends CoreRootContext { this._typeExtractor = typeExtractor; this._typeResolver = typeResolver; this._constantPoolFactory = constantPoolFactory; - this._actionFactory = actionFactory + this._actionFactory = actionFactory; } get rootPath(): string { @@ -141,8 +141,8 @@ export class RootContext extends CoreRootContext { } async getActions(filePath: string) { - const factory = new ActionFactory(1000) - return await factory.extract(filePath, this.getSource(filePath)) + const factory = new ActionFactory(1000); + return await factory.extract(filePath, this.getSource(filePath)); } getExports(filePath: string): Export[] { @@ -173,14 +173,14 @@ export class RootContext extends CoreRootContext { async getAllActions() { if (!this._actionMap) { - this._actionMap = new Map() + this._actionMap = new Map(); for (const filepath of this._analysisFiles) { - this._actionMap.set(filepath, await this.getActions(filepath)) + this._actionMap.set(filepath, await this.getActions(filepath)); } } - - return this._actionMap + + return this._actionMap; } getAllExports(): Map { @@ -380,6 +380,6 @@ export class RootContext extends CoreRootContext { } exit() { - this._actionFactory.exit() + this._actionFactory.exit(); } } diff --git a/libraries/analysis-javascript/lib/dynamic/action/Action.ts b/libraries/analysis-javascript/lib/dynamic/action/Action.ts index c2fed1df3..7cadad55d 100644 --- a/libraries/analysis-javascript/lib/dynamic/action/Action.ts +++ b/libraries/analysis-javascript/lib/dynamic/action/Action.ts @@ -17,11 +17,18 @@ */ export type Action = { - id: string - type: ActionType - children: { - [key: string]: Action - } -} + id: string; + type: ActionType; + children: { + [key: string]: Action; + }; +}; -export type ActionType = 'function' | 'method' | 'class' | 'object' | 'constructor' | 'setter' | 'getter' \ No newline at end of file +export type ActionType = + | "function" + | "method" + | "class" + | "object" + | "constructor" + | "setter" + | "getter"; diff --git a/libraries/analysis-javascript/lib/dynamic/action/ActionFactory.ts b/libraries/analysis-javascript/lib/dynamic/action/ActionFactory.ts index 71cc75cb2..e6e04e969 100644 --- a/libraries/analysis-javascript/lib/dynamic/action/ActionFactory.ts +++ b/libraries/analysis-javascript/lib/dynamic/action/ActionFactory.ts @@ -25,73 +25,73 @@ import { Action } from "./Action"; import { ExecuteMessage, ResultMessage } from "./Executor"; export class ActionFactory { - protected static LOGGER: Logger; + protected static LOGGER: Logger; - private executionTimeout: number - private _process: ChildProcess; + private executionTimeout: number; + private _process: ChildProcess; - constructor(executionTimeout: number) { - ActionFactory.LOGGER = getLogger(ActionFactory.name); + constructor(executionTimeout: number) { + ActionFactory.LOGGER = getLogger(ActionFactory.name); - this.executionTimeout = executionTimeout - // eslint-disable-next-line unicorn/prefer-module - this._process = fork(path.join(__dirname, "Executor.js")); - console.log('created') - } + this.executionTimeout = executionTimeout; + // eslint-disable-next-line unicorn/prefer-module + this._process = fork(path.join(__dirname, "Executor.js")); + console.log("created"); + } - exit() { - if (this._process) { - this._process.kill() - } + exit() { + if (this._process) { + this._process.kill(); } + } - async extract(filePath: string, source: string) { - // try catch maybe? - return await this._extract(filePath, source) - } + async extract(filePath: string, source: string) { + // try catch maybe? + return await this._extract(filePath, source); + } - private async _extract(filePath: string, source: string): Promise { - if (!this._process.connected || this._process.killed) { - // eslint-disable-next-line unicorn/prefer-module - this._process = fork(path.join(__dirname, "Executor.js")); - } - const childProcess = this._process; - - return await new Promise((resolve, reject) => { - const timeout = setTimeout(() => { - ActionFactory.LOGGER.warn( - `Execution timeout reached killing process, timeout: ${this.executionTimeout}` - ); - childProcess.removeAllListeners(); - childProcess.kill(); - reject("timeout"); - }, this.executionTimeout); - - childProcess.on("message", (message: ResultMessage) => { - if (typeof message !== "object") { - return reject( - new TypeError("Invalid data received from child process") - ); - } - - if (message.message === "result") { - childProcess.removeAllListeners(); - clearTimeout(timeout); - return resolve(message.actions); - } - }); - - childProcess.on("error", (error) => { - reject(error); - }); - - const executeMessage: ExecuteMessage = { - message: 'execute', - filePath: filePath, - source: source - } - - childProcess.send(executeMessage); - }); + private async _extract(filePath: string, source: string): Promise { + if (!this._process.connected || this._process.killed) { + // eslint-disable-next-line unicorn/prefer-module + this._process = fork(path.join(__dirname, "Executor.js")); } -} \ No newline at end of file + const childProcess = this._process; + + return await new Promise((resolve, reject) => { + const timeout = setTimeout(() => { + ActionFactory.LOGGER.warn( + `Execution timeout reached killing process, timeout: ${this.executionTimeout}` + ); + childProcess.removeAllListeners(); + childProcess.kill(); + reject("timeout"); + }, this.executionTimeout); + + childProcess.on("message", (message: ResultMessage) => { + if (typeof message !== "object") { + return reject( + new TypeError("Invalid data received from child process") + ); + } + + if (message.message === "result") { + childProcess.removeAllListeners(); + clearTimeout(timeout); + return resolve(message.actions); + } + }); + + childProcess.on("error", (error) => { + reject(error); + }); + + const executeMessage: ExecuteMessage = { + message: "execute", + filePath: filePath, + source: source, + }; + + childProcess.send(executeMessage); + }); + } +} diff --git a/libraries/analysis-javascript/lib/dynamic/action/Executor.ts b/libraries/analysis-javascript/lib/dynamic/action/Executor.ts index d084614d7..49d6272e9 100644 --- a/libraries/analysis-javascript/lib/dynamic/action/Executor.ts +++ b/libraries/analysis-javascript/lib/dynamic/action/Executor.ts @@ -21,70 +21,79 @@ import { Session } from "node:inspector"; import { Action, ActionType } from "./Action"; export type ExecuteMessage = { - message: 'execute', - filePath: string - source: string -} + message: "execute"; + filePath: string; + source: string; +}; export type ResultMessage = { - message: 'result', - actions: Action[] -} + message: "result"; + actions: Action[]; +}; // eslint-disable-next-line @typescript-eslint/no-misused-promises process.on("message", async (data: ExecuteMessage) => { - if (typeof data !== "object") { - throw new TypeError("Invalid data received from child process"); - } - if (data.message === "execute") { - await gatherIntel(data.filePath, data.source); + if (typeof data !== "object") { + throw new TypeError("Invalid data received from child process"); + } + if (data.message === "execute") { + await gatherIntel(data.filePath, data.source); + } +}); + +function getLineNumber(text: string, index: number) { + let line = 1; + let column = 1; + for (let index_ = 0; index_ < index; index_++) { + column++; + if (text[index_] === "\n") { + column = 1; + line++; } - }); - - function getLineNumber(text: string, index: number) { - let line = 1; - let column = 1; - for (let index_ = 0; index_ < index; index_++) { - column++ - if (text[index_] === '\n') { - column = 1 - line++; - } - if (text[index_] === '\r') { - // A line feed after a carriage return counts as part of the same newline - if (text[index_ + 1] === '\n') { - index_++; - } - column = 1 - line++; + if (text[index_] === "\r") { + // A line feed after a carriage return counts as part of the same newline + if (text[index_ + 1] === "\n") { + index_++; } + column = 1; + line++; } - return {line, column}; } + return { line, column }; +} function isClass(object: any) { - if (object == undefined || object === undefined) { return false; } - - const isCtorClass = object.constructor - && object.constructor.toString().slice(0, 5) === 'class' - // const isNativeCtorClass= object.constructor && - // object.constructor.name != "Function" && - // object.constructor.name in global; - // console.log(isCtorClass, isNativeCtorClass) - if (object.prototype === undefined) { - return isCtorClass// || isNativeCtorClass - } - const isPrototypeCtorClass = object.prototype.constructor - && object.prototype.constructor.toString - && object.prototype.constructor.toString().slice(0, 5) === 'class' + if (object == undefined || object === undefined) { + return false; + } - const isNativePrototypeCtorClass = object.prototype.constructor.name in global && ( - (global)[object.prototype.constructor.name] == object.constructor || - (global)[object.prototype.constructor.name] == object - ); + const isCtorClass = + object.constructor && object.constructor.toString().slice(0, 5) === "class"; + // const isNativeCtorClass= object.constructor && + // object.constructor.name != "Function" && + // object.constructor.name in global; + // console.log(isCtorClass, isNativeCtorClass) + if (object.prototype === undefined) { + return isCtorClass; // || isNativeCtorClass + } + const isPrototypeCtorClass = + object.prototype.constructor && + object.prototype.constructor.toString && + object.prototype.constructor.toString().slice(0, 5) === "class"; - const hasPrototypes = object.prototype && Object.keys(object.prototype).length > 0 - return isCtorClass || isPrototypeCtorClass || isNativePrototypeCtorClass || hasPrototypes + const isNativePrototypeCtorClass = + object.prototype.constructor.name in global && + ((global)[object.prototype.constructor.name] == object.constructor || + (global)[object.prototype.constructor.name] == object); + + const hasPrototypes = + object.prototype && Object.keys(object.prototype).length > 0; + return ( + isCtorClass || + isPrototypeCtorClass || + isNativePrototypeCtorClass || + hasPrototypes + ); } // function getAllFuncs(toCheck: any) { @@ -93,8 +102,8 @@ function isClass(object: any) { // do { // properties.push(...Object.getOwnPropertyNames(object)); // } while (((object = Object.getPrototypeOf(object)) && object != Object.prototype)); - -// return properties.sort().filter((e, index, array) => { + +// return properties.sort().filter((e, index, array) => { // if (Object.prototype.hasOwnProperty(e)) { // return false // } @@ -107,152 +116,171 @@ function getAllMethodNames(object: any) { const methods = new Set(); if (!(object instanceof Object)) { - return methods + return methods; } do { - const keys = Reflect.ownKeys(object) + const keys = Reflect.ownKeys(object); for (const k of keys) methods.add(k); - } while (((object = Reflect.getPrototypeOf(object)) && object != Object.prototype)) + } while ( + (object = Reflect.getPrototypeOf(object)) && + object != Object.prototype + ); return methods; } - + async function gatherIntel(filePath: string, source: string) { - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment - const import_ = await import(filePath) - - let rootAction: Action - - const session = new Session() - session.connect() - let objectId - session.post('Runtime.evaluate', { expression: source }, (error, {result}) => { - console.log(result) - objectId = result.objectId - }) - session.post('Runtime.getProperties', { objectId }, (error, {internalProperties}) => { - console.log(internalProperties) - }) - console.log('aaaa') - // queue of [parent, key, child] - const queue: [Action, string, unknown][] = [[undefined, undefined, import_]] - - while (queue.length > 0) { - const [parent, key, child] = queue.shift() - - if (!(child instanceof Object)) { - continue - } + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + const import_ = await import(filePath); - console.log(child) - const text: string = child.toString() - const startIndex = source.indexOf(text) - - let id: string - let type: ActionType - if (startIndex === -1) { - // its actually an object or class - console.log('no index') - console.log(child.constructor) - console.log() - id = isClass(child) ? 'class' : 'object' - type = isClass(child) ? 'class' : 'object' - } else { - const endIndex = startIndex + text.length - const {line: startLine, column: startColumn} = getLineNumber(source, startIndex) - const {line: endLine, column: endColumn} = getLineNumber(source, endIndex) - id = `${filePath}:${startLine}:${startColumn}:::${endLine}:${endColumn}:::${startIndex}:${endIndex}` - type = isClass(child) ? 'class' : 'function' - } + let rootAction: Action; - const childAction: Action = { - id: id, - type: type, - children: {} - } + const session = new Session(); + session.connect(); + let objectId; + session.post( + "Runtime.evaluate", + { expression: source }, + (error, { result }) => { + console.log(result); + objectId = result.objectId; + } + ); + session.post( + "Runtime.getProperties", + { objectId }, + (error, { internalProperties }) => { + console.log(internalProperties); + } + ); + console.log("aaaa"); + // queue of [parent, key, child] + const queue: [Action, string, unknown][] = [[undefined, undefined, import_]]; - if (!rootAction) { - rootAction = childAction - } - - if (parent) { - parent.children[key] = childAction - } + while (queue.length > 0) { + const [parent, key, child] = queue.shift(); - console.log('== keys') - console.log(Object.keys(child)) - for (const key of Object.keys(child)) { - if (key === 'prototype') { - continue - } - queue.push([childAction, key, (child)[key]]) - } + if (!(child instanceof Object)) { + continue; + } + + console.log(child); + const text: string = child.toString(); + const startIndex = source.indexOf(text); + + let id: string; + let type: ActionType; + if (startIndex === -1) { + // its actually an object or class + console.log("no index"); + console.log(child.constructor); + console.log(); + id = isClass(child) ? "class" : "object"; + type = isClass(child) ? "class" : "object"; + } else { + const endIndex = startIndex + text.length; + const { line: startLine, column: startColumn } = getLineNumber( + source, + startIndex + ); + const { line: endLine, column: endColumn } = getLineNumber( + source, + endIndex + ); + id = `${filePath}:${startLine}:${startColumn}:::${endLine}:${endColumn}:::${startIndex}:${endIndex}`; + type = isClass(child) ? "class" : "function"; + } + + const childAction: Action = { + id: id, + type: type, + children: {}, + }; + + if (!rootAction) { + rootAction = childAction; + } + + if (parent) { + parent.children[key] = childAction; + } - // console.log('== prototypes') - // for (const key in (child).prototype) { - // console.log(key) - // queue.push([childAction, key, (child).prototype[key]]) - // } - console.log('== properties') - - const properties: Set = getAllMethodNames((child).prototype) - // const defaultProperties = Object.getOwnPropertyNames(Object.getPrototypeOf(Object)) - console.log(properties) - for (const key of properties) { - if ((child).prototype[key] === child) { - continue - } - queue.push([childAction, key.toString(), (child).prototype[key]]) + console.log("== keys"); + console.log(Object.keys(child)); + for (const key of Object.keys(child)) { + if (key === "prototype") { + continue; } - console.log('== end') - // for (const key of Object.keys(child)) { - // queue.push([childAction, (child)[key]]) - // } - - // if (isClass(child)) { - // // Object. - // for (const key of Object.getOwnPropertyNames((child).prototype)) { - // queue.push([childAction, (child).prototype[key]]) - // } - - // } + queue.push([childAction, key, (child)[key]]); } - // console.log(import_) - // for (const x of Object.keys(import_)) { - // if (typeof import_[x] !== 'function') { - // return - // } - // let type: ActionType - // console.log(x) - // console.log(import_[x]) - // console.log(Object.keys(import_[x].prototype)) - // if (import_[x].constructor) { - // console.log(import_[x].constructor) - // type = 'class' - // } else { - // type = 'function' - // } - - // const text: string = import_[x].toString() - // const startIndex = source.indexOf(text) - // const endIndex = startIndex + text.length - // const {line: startLine, column: startColumn} = getLineNumber(source, startIndex) - // const {line: endLine, column: endColumn} = getLineNumber(source, endIndex) - // const id = `${filePath}:${startLine}:${startColumn}:::${endLine}:${endColumn}:::${startIndex}:${endIndex}` - - // actions.push({ - // id: id, - // type: type, - // children: [] - // }) + // console.log('== prototypes') + // for (const key in (child).prototype) { + // console.log(key) + // queue.push([childAction, key, (child).prototype[key]]) // } - console.log(rootAction) - console.log(JSON.stringify(rootAction, undefined, 2)) + console.log("== properties"); - const resultMessage: ResultMessage = { - message: 'result', - actions: [rootAction] + const properties: Set = getAllMethodNames( + (child).prototype + ); + // const defaultProperties = Object.getOwnPropertyNames(Object.getPrototypeOf(Object)) + console.log(properties); + for (const key of properties) { + if ((child).prototype[key] === child) { + continue; + } + queue.push([childAction, key.toString(), (child).prototype[key]]); } - process.send(resultMessage) + console.log("== end"); + // for (const key of Object.keys(child)) { + // queue.push([childAction, (child)[key]]) + // } + + // if (isClass(child)) { + // // Object. + // for (const key of Object.getOwnPropertyNames((child).prototype)) { + // queue.push([childAction, (child).prototype[key]]) + // } + + // } + } + + // console.log(import_) + // for (const x of Object.keys(import_)) { + // if (typeof import_[x] !== 'function') { + // return + // } + // let type: ActionType + // console.log(x) + // console.log(import_[x]) + // console.log(Object.keys(import_[x].prototype)) + // if (import_[x].constructor) { + // console.log(import_[x].constructor) + // type = 'class' + // } else { + // type = 'function' + // } + + // const text: string = import_[x].toString() + // const startIndex = source.indexOf(text) + // const endIndex = startIndex + text.length + // const {line: startLine, column: startColumn} = getLineNumber(source, startIndex) + // const {line: endLine, column: endColumn} = getLineNumber(source, endIndex) + // const id = `${filePath}:${startLine}:${startColumn}:::${endLine}:${endColumn}:::${startIndex}:${endIndex}` + + // actions.push({ + // id: id, + // type: type, + // children: [] + // }) + // } + console.log(rootAction); + console.log(JSON.stringify(rootAction, undefined, 2)); + + const resultMessage: ResultMessage = { + message: "result", + actions: [rootAction], + }; + process.send(resultMessage); } diff --git a/tools/javascript/lib/JavaScriptLauncher.ts b/tools/javascript/lib/JavaScriptLauncher.ts index 29fbbe9b2..6d7b724dd 100644 --- a/tools/javascript/lib/JavaScriptLauncher.ts +++ b/tools/javascript/lib/JavaScriptLauncher.ts @@ -31,7 +31,7 @@ import { TargetFactory, TypeExtractor, TypeModelFactory, - ActionFactory + ActionFactory, } from "@syntest/analysis-javascript"; import { ArgumentsObject, @@ -202,7 +202,9 @@ export class JavaScriptLauncher extends Launcher { this.arguments_.analysisExclude ); - const actionFactory = new ActionFactory((this.arguments_).executionTimeout) + const actionFactory = new ActionFactory( + (this.arguments_).executionTimeout + ); this.rootContext = new RootContext( this.arguments_.targetRootDirectory, @@ -423,7 +425,7 @@ export class JavaScriptLauncher extends Launcher { ); JavaScriptLauncher.LOGGER.info("Gathering actions"); - await this.rootContext.getAllActions() + await this.rootContext.getAllActions(); const startTypeResolving = Date.now(); JavaScriptLauncher.LOGGER.info("Extracting types"); @@ -925,7 +927,7 @@ export class JavaScriptLauncher extends Launcher { this.runner.process.kill(); } if (this.rootContext) { - this.rootContext.exit() + this.rootContext.exit(); } // TODO should be cleanup step in tool // Finish From 78a920a26c71982c481beac674546169a8373740 Mon Sep 17 00:00:00 2001 From: Dimitri Stallenberg Date: Wed, 11 Oct 2023 12:07:34 +0200 Subject: [PATCH 4/4] feat: add changes --- .../analysis-javascript/lib/RootContext.ts | 18 +- .../lib/dynamic/action/Action.ts | 19 +- .../lib/dynamic/action/ActionFactory.ts | 21 +- .../lib/dynamic/action/Executor.ts | 286 +++++---- .../lib/target/export/ExpressionStatement.ts | 1 - libraries/search-javascript/index.ts | 16 +- .../lib/search/JavaScriptSubject.ts | 30 +- .../lib/search/crossover/TreeCrossover.ts | 25 +- .../lib/testbuilding/ContextBuilder.ts | 89 +-- .../lib/testbuilding/JavaScriptDecoder.ts | 22 +- .../lib/testcase/JavaScriptTestCase.ts | 4 +- .../lib/testcase/StatementPool.ts | 38 +- .../ExecutionInformationIntegrator.ts | 8 +- .../sampling/JavaScriptRandomSampler.ts | 585 ++++++------------ .../sampling/JavaScriptTestCaseSampler.ts | 192 +++--- .../testcase/sampling/generators/Generator.ts | 67 -- .../generators/action/CallGenerator.ts | 58 -- .../action/ConstantObjectGenerator.ts | 58 -- .../action/ConstructorCallGenerator.ts | 67 -- .../action/FunctionCallGenerator.ts | 54 -- .../generators/action/MethodCallGenerator.ts | 55 -- .../action/ObjectFunctionCallGenerator.ts | 55 -- .../generators/action/SetterGenerator.ts | 59 -- .../Reference.ts} | 30 +- .../lib/testcase/statements/Statement.ts | 32 +- .../statements/action/ActionStatement.ts | 68 +- .../statements/action/ClassActionStatement.ts | 87 --- .../statements/action/ConstructorCall.ts | 68 +- .../statements/action/FunctionCall.ts | 98 +-- .../{ConstantObject.ts => ImportStatement.ts} | 57 +- .../action/{Getter.ts => MemberStatement.ts} | 81 +-- .../testcase/statements/action/MethodCall.ts | 119 ---- .../statements/action/ObjectFunctionCall.ts | 154 ----- .../lib/testcase/statements/action/Setter.ts | 121 ---- .../statements/complex/ArrayStatement.ts | 19 +- .../complex/ArrowFunctionStatement.ts | 29 +- .../statements/complex/ObjectStatement.ts | 24 +- .../{primitive => literal}/BoolStatement.ts | 9 +- .../IntegerStatement.ts | 11 +- .../LiteralStatement.ts} | 19 +- .../{primitive => literal}/NullStatement.ts | 11 +- .../NumericStatement.ts | 11 +- .../{primitive => literal}/StringStatement.ts | 14 +- .../UndefinedStatement.ts | 11 +- tools/javascript/lib/JavaScriptLauncher.ts | 13 +- 45 files changed, 894 insertions(+), 2019 deletions(-) delete mode 100644 libraries/search-javascript/lib/testcase/sampling/generators/Generator.ts delete mode 100644 libraries/search-javascript/lib/testcase/sampling/generators/action/CallGenerator.ts delete mode 100644 libraries/search-javascript/lib/testcase/sampling/generators/action/ConstantObjectGenerator.ts delete mode 100644 libraries/search-javascript/lib/testcase/sampling/generators/action/ConstructorCallGenerator.ts delete mode 100644 libraries/search-javascript/lib/testcase/sampling/generators/action/FunctionCallGenerator.ts delete mode 100644 libraries/search-javascript/lib/testcase/sampling/generators/action/MethodCallGenerator.ts delete mode 100644 libraries/search-javascript/lib/testcase/sampling/generators/action/ObjectFunctionCallGenerator.ts delete mode 100644 libraries/search-javascript/lib/testcase/sampling/generators/action/SetterGenerator.ts rename libraries/search-javascript/lib/testcase/{sampling/generators/action/GetterGenerator.ts => statements/Reference.ts} (50%) delete mode 100644 libraries/search-javascript/lib/testcase/statements/action/ClassActionStatement.ts rename libraries/search-javascript/lib/testcase/statements/action/{ConstantObject.ts => ImportStatement.ts} (57%) rename libraries/search-javascript/lib/testcase/statements/action/{Getter.ts => MemberStatement.ts} (52%) delete mode 100644 libraries/search-javascript/lib/testcase/statements/action/MethodCall.ts delete mode 100644 libraries/search-javascript/lib/testcase/statements/action/ObjectFunctionCall.ts delete mode 100644 libraries/search-javascript/lib/testcase/statements/action/Setter.ts rename libraries/search-javascript/lib/testcase/statements/{primitive => literal}/BoolStatement.ts (91%) rename libraries/search-javascript/lib/testcase/statements/{primitive => literal}/IntegerStatement.ts (94%) rename libraries/search-javascript/lib/testcase/statements/{primitive/PrimitiveStatement.ts => literal/LiteralStatement.ts} (84%) rename libraries/search-javascript/lib/testcase/statements/{primitive => literal}/NullStatement.ts (90%) rename libraries/search-javascript/lib/testcase/statements/{primitive => literal}/NumericStatement.ts (94%) rename libraries/search-javascript/lib/testcase/statements/{primitive => literal}/StringStatement.ts (95%) rename libraries/search-javascript/lib/testcase/statements/{primitive => literal}/UndefinedStatement.ts (90%) diff --git a/libraries/analysis-javascript/lib/RootContext.ts b/libraries/analysis-javascript/lib/RootContext.ts index 38c5b998c..43a93560a 100644 --- a/libraries/analysis-javascript/lib/RootContext.ts +++ b/libraries/analysis-javascript/lib/RootContext.ts @@ -70,7 +70,7 @@ export class RootContext extends CoreRootContext { // Mapping: filepath -> target name -> Exports protected _exportMap: Map; - protected _actionMap: Map; + protected _actionMap: Map>; constructor( rootPath: string, @@ -140,9 +140,9 @@ export class RootContext extends CoreRootContext { return this._sources.get(absoluteTargetPath); } - async getActions(filePath: string) { + protected async getActions(filePath: string) { const factory = new ActionFactory(1000); - return await factory.extract(filePath, this.getSource(filePath)); + return await factory.extract(filePath, this.getSource(filePath), this.getAbstractSyntaxTree(filePath)); } getExports(filePath: string): Export[] { @@ -171,13 +171,23 @@ export class RootContext extends CoreRootContext { return this._exportMap.get(absolutePath); } - async getAllActions() { + async extractAllActions() { if (!this._actionMap) { this._actionMap = new Map(); for (const filepath of this._analysisFiles) { this._actionMap.set(filepath, await this.getActions(filepath)); } + + this._actionFactory.exit() + } + + return this._actionMap; + } + + getAllActions() { + if (!this._actionMap) { + throw new Error("First call extractAllActions before calling getAllActions") } return this._actionMap; diff --git a/libraries/analysis-javascript/lib/dynamic/action/Action.ts b/libraries/analysis-javascript/lib/dynamic/action/Action.ts index 7cadad55d..1014e472f 100644 --- a/libraries/analysis-javascript/lib/dynamic/action/Action.ts +++ b/libraries/analysis-javascript/lib/dynamic/action/Action.ts @@ -17,18 +17,25 @@ */ export type Action = { - id: string; type: ActionType; + + // identification + id: string; + filePath: string; + // location: Location; ?? + + // ancestory children: { [key: string]: Action; }; + parentId: string | undefined + + // properties + constructable: boolean + name: string }; export type ActionType = | "function" - | "method" - | "class" | "object" - | "constructor" - | "setter" - | "getter"; + | "constant" // maybe nice diff --git a/libraries/analysis-javascript/lib/dynamic/action/ActionFactory.ts b/libraries/analysis-javascript/lib/dynamic/action/ActionFactory.ts index e6e04e969..3dc12d45d 100644 --- a/libraries/analysis-javascript/lib/dynamic/action/ActionFactory.ts +++ b/libraries/analysis-javascript/lib/dynamic/action/ActionFactory.ts @@ -19,6 +19,7 @@ import { ChildProcess, fork } from "node:child_process"; import * as path from "node:path"; +import * as t from "@babel/types"; import { getLogger, Logger } from "@syntest/logging"; import { Action } from "./Action"; @@ -36,7 +37,6 @@ export class ActionFactory { this.executionTimeout = executionTimeout; // eslint-disable-next-line unicorn/prefer-module this._process = fork(path.join(__dirname, "Executor.js")); - console.log("created"); } exit() { @@ -45,19 +45,19 @@ export class ActionFactory { } } - async extract(filePath: string, source: string) { + async extract(filePath: string, source: string, ast: t.Node) { // try catch maybe? - return await this._extract(filePath, source); + return await this._extract(filePath, source, ast); } - private async _extract(filePath: string, source: string): Promise { + private async _extract(filePath: string, source: string, ast: t.Node): Promise> { if (!this._process.connected || this._process.killed) { // eslint-disable-next-line unicorn/prefer-module this._process = fork(path.join(__dirname, "Executor.js")); } const childProcess = this._process; - return await new Promise((resolve, reject) => { + return await new Promise>((resolve, reject) => { const timeout = setTimeout(() => { ActionFactory.LOGGER.warn( `Execution timeout reached killing process, timeout: ${this.executionTimeout}` @@ -77,7 +77,15 @@ export class ActionFactory { if (message.message === "result") { childProcess.removeAllListeners(); clearTimeout(timeout); - return resolve(message.actions); + + // + const actionMap = new Map() + + for (const key of Object.keys(message.actions)) { + actionMap.set(key, message.actions[key]) + } + + return resolve(actionMap); } }); @@ -89,6 +97,7 @@ export class ActionFactory { message: "execute", filePath: filePath, source: source, + ast: ast }; childProcess.send(executeMessage); diff --git a/libraries/analysis-javascript/lib/dynamic/action/Executor.ts b/libraries/analysis-javascript/lib/dynamic/action/Executor.ts index 49d6272e9..7dda89bb6 100644 --- a/libraries/analysis-javascript/lib/dynamic/action/Executor.ts +++ b/libraries/analysis-javascript/lib/dynamic/action/Executor.ts @@ -16,7 +16,8 @@ * limitations under the License. */ -import { Session } from "node:inspector"; +import { NodePath, traverse } from "@babel/core"; +import * as t from "@babel/types"; import { Action, ActionType } from "./Action"; @@ -24,11 +25,12 @@ export type ExecuteMessage = { message: "execute"; filePath: string; source: string; + ast: t.Node; }; export type ResultMessage = { message: "result"; - actions: Action[]; + actions: {[key: string]: Action}; }; // eslint-disable-next-line @typescript-eslint/no-misused-promises @@ -37,17 +39,17 @@ process.on("message", async (data: ExecuteMessage) => { throw new TypeError("Invalid data received from child process"); } if (data.message === "execute") { - await gatherIntel(data.filePath, data.source); + await gatherIntel(data.filePath, data.source, data.ast); } }); -function getLineNumber(text: string, index: number) { +function getLineAndColumn(text: string, index: number) { let line = 1; - let column = 1; + let column = 0; for (let index_ = 0; index_ < index; index_++) { column++; if (text[index_] === "\n") { - column = 1; + column = 0; line++; } if (text[index_] === "\r") { @@ -55,7 +57,7 @@ function getLineNumber(text: string, index: number) { if (text[index_ + 1] === "\n") { index_++; } - column = 1; + column = 0; line++; } } @@ -69,12 +71,12 @@ function isClass(object: any) { const isCtorClass = object.constructor && object.constructor.toString().slice(0, 5) === "class"; - // const isNativeCtorClass= object.constructor && - // object.constructor.name != "Function" && - // object.constructor.name in global; - // console.log(isCtorClass, isNativeCtorClass) - if (object.prototype === undefined) { - return isCtorClass; // || isNativeCtorClass + const isNativeCtorClass= object.constructor && + object.constructor.name != "Function" && + object.constructor.name in global; + + if (object.prototype === undefined) { + return isCtorClass || isNativeCtorClass } const isPrototypeCtorClass = object.prototype.constructor && @@ -90,6 +92,7 @@ function isClass(object: any) { object.prototype && Object.keys(object.prototype).length > 0; return ( isCtorClass || + isNativeCtorClass || isPrototypeCtorClass || isNativePrototypeCtorClass || hasPrototypes @@ -112,6 +115,32 @@ function isClass(object: any) { // }); // } +function isConstructable(object: any) { + if (object == undefined || object === undefined) { + return false; + } + + const handler={construct(){return handler}} //Must return ANY object, so reuse one + + // try{ + // return !!(new (new Proxy(object, handler))()) + // }catch{ + // return false + // } + + try { + Reflect.construct(String, [], object); + } catch { + return false; + } + return true; + // const properties: Set = getAllMethodNames( + // object.prototype + // ); + // return object.hasOwnProperty('prototype') && (Object.keys(object).length > 0 || properties.size > 0) + // return !!object.constructor +} + function getAllMethodNames(object: any) { const methods = new Set(); @@ -129,158 +158,189 @@ function getAllMethodNames(object: any) { return methods; } -async function gatherIntel(filePath: string, source: string) { - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment - const import_ = await import(filePath); +function getSourceCodeLocationId(filePath: string, source: string, ast: t.Node, parentAction: Action, key: string, object: any): string { + const text: string = object.toString(); + + if (key === 'constructor') { + let id: string + traverse(ast, { + ClassMethod: { + enter: (path: NodePath) => { + if (path.node.kind !== 'constructor') { + return + } + const classParent = path.parentPath.parentPath + const [start, end] = parentAction.id.split(':::')[2].split(':') + if (classParent.isClass() && classParent.node.start === Number.parseInt(start) && classParent.node.end === Number.parseInt(end)) { + id = `${filePath}:${path.node.loc.start.line}:${path.node.loc.start.column}:::${path.node.loc.end.line}:${path.node.loc.end.column}:::${path.node.loc.start.index}:${path.node.loc.end.index}`; + } + } + } + }); - let rootAction: Action; - const session = new Session(); - session.connect(); - let objectId; - session.post( - "Runtime.evaluate", - { expression: source }, - (error, { result }) => { - console.log(result); - objectId = result.objectId; - } - ); - session.post( - "Runtime.getProperties", - { objectId }, - (error, { internalProperties }) => { - console.log(internalProperties); - } - ); - console.log("aaaa"); - // queue of [parent, key, child] - const queue: [Action, string, unknown][] = [[undefined, undefined, import_]]; + return id + // // terrible solution to extract a constructor + // // TODO this only prevents directly commented code + // // TODO /* \n\n\n constructor ... */ is also commented but will be picked up by this + // // TODO this regex also only allows white space between the ) and { at the end of a constructor + // const regx = /(? 0) { - const [parent, key, child] = queue.shift(); + // console.log(match) + // if (!match) { + // return undefined + // } - if (!(child instanceof Object)) { - continue; - } + // const startIndex = match.index + // const value = match[0] + // console.log(value) + // let endIndex = startIndex + value.length + // // find the closing bracket + // let depth = 1 + // for (;depth > 0;endIndex++) { + // if (!source[endIndex]) { + // throw new Error("Cannot find closing bracket") + // } + // if (source[endIndex] === '{') { + // depth++ + // } else if (source[endIndex] === '}') { + // depth-- + // } + // } - console.log(child); - const text: string = child.toString(); + // const { line: startLine, column: startColumn } = getLineAndColumn( + // source, + // startIndex + // ); + // const { line: endLine, column: endColumn } = getLineAndColumn( + // source, + // endIndex + // ); + + // return `${filePath}:${startLine}:${startColumn}:::${endLine}:${endColumn}:::${startIndex}:${endIndex}`; + } else { const startIndex = source.indexOf(text); - let id: string; - let type: ActionType; + if (source.lastIndexOf(text) !== startIndex) { + console.warn("Multiple occurences of the same function/object found in export! We do not support this! (Additionally, duplicate code is not something you want!)") + } + + let id: string if (startIndex === -1) { + console.log('could not find the following:') + console.log(object.toString()) // its actually an object or class - console.log("no index"); - console.log(child.constructor); - console.log(); - id = isClass(child) ? "class" : "object"; - type = isClass(child) ? "class" : "object"; + id = isClass(object) ? "object" : "function"; } else { const endIndex = startIndex + text.length; - const { line: startLine, column: startColumn } = getLineNumber( + const { line: startLine, column: startColumn } = getLineAndColumn( source, startIndex ); - const { line: endLine, column: endColumn } = getLineNumber( + const { line: endLine, column: endColumn } = getLineAndColumn( source, endIndex ); id = `${filePath}:${startLine}:${startColumn}:::${endLine}:${endColumn}:::${startIndex}:${endIndex}`; - type = isClass(child) ? "class" : "function"; } + return id + } +} + +async function gatherIntel(filePath: string, source: string, ast: t.Node) { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + const import_ = await import(filePath); + + let rootAction: Action; + const actions: {[key: string]: Action} = {} + + // queue of [parent, key, child] + const queue: [Action, string, unknown][] = [[undefined, "__root__", import_]]; + + while (queue.length > 0) { + const [parentAction, key, child] = queue.shift(); + + // console.log(child) + if (!(child instanceof Object)) { + // TODO could be an constant! + continue; + } + + const id: string = getSourceCodeLocationId(filePath, source, ast, parentAction, key, child) + + if (!id) { + // probably non-defined constructor + continue + } + + if (actions[id]) { + // no repeats + continue + } + + const type: ActionType = typeof child === 'function' ? 'function' : 'object'//typeof child === 'object' ? 'object' : 'constant' const childAction: Action = { - id: id, type: type, + + id: id, + filePath: filePath, + children: {}, + parentId: parentAction ? parentAction.id : undefined, + + constructable: isConstructable(child) && key !== 'constructor', + name: key }; + actions[id] = childAction if (!rootAction) { rootAction = childAction; } - if (parent) { - parent.children[key] = childAction; + if (parentAction) { + parentAction.children[key] = childAction; + } + + if (isConstructable(child) && key !== 'constructor') { + queue.push([childAction, 'constructor', (child).prototype['constructor']]) + } + + if (key === 'constructor') { + continue } - console.log("== keys"); - console.log(Object.keys(child)); for (const key of Object.keys(child)) { if (key === "prototype") { continue; } + if ((child)[key] === child) { + continue + } queue.push([childAction, key, (child)[key]]); } - // console.log('== prototypes') - // for (const key in (child).prototype) { - // console.log(key) - // queue.push([childAction, key, (child).prototype[key]]) - // } - console.log("== properties"); - const properties: Set = getAllMethodNames( (child).prototype ); - // const defaultProperties = Object.getOwnPropertyNames(Object.getPrototypeOf(Object)) - console.log(properties); for (const key of properties) { if ((child).prototype[key] === child) { continue; } queue.push([childAction, key.toString(), (child).prototype[key]]); } - console.log("== end"); - // for (const key of Object.keys(child)) { - // queue.push([childAction, (child)[key]]) - // } - - // if (isClass(child)) { - // // Object. - // for (const key of Object.getOwnPropertyNames((child).prototype)) { - // queue.push([childAction, (child).prototype[key]]) - // } - - // } } - // console.log(import_) - // for (const x of Object.keys(import_)) { - // if (typeof import_[x] !== 'function') { - // return - // } - // let type: ActionType - // console.log(x) - // console.log(import_[x]) - // console.log(Object.keys(import_[x].prototype)) - // if (import_[x].constructor) { - // console.log(import_[x].constructor) - // type = 'class' - // } else { - // type = 'function' - // } - - // const text: string = import_[x].toString() - // const startIndex = source.indexOf(text) - // const endIndex = startIndex + text.length - // const {line: startLine, column: startColumn} = getLineNumber(source, startIndex) - // const {line: endLine, column: endColumn} = getLineNumber(source, endIndex) - // const id = `${filePath}:${startLine}:${startColumn}:::${endLine}:${endColumn}:::${startIndex}:${endIndex}` - - // actions.push({ - // id: id, - // type: type, - // children: [] - // }) - // } - console.log(rootAction); - console.log(JSON.stringify(rootAction, undefined, 2)); + // console.log(rootAction); + // console.log(JSON.stringify(rootAction, undefined, 2)); const resultMessage: ResultMessage = { message: "result", - actions: [rootAction], + actions: actions, }; + // console.log(resultMessage) + // console.log(JSON.stringify(resultMessage, undefined, 2)); + process.send(resultMessage); } diff --git a/libraries/analysis-javascript/lib/target/export/ExpressionStatement.ts b/libraries/analysis-javascript/lib/target/export/ExpressionStatement.ts index 1b0289c03..3e5d64c16 100644 --- a/libraries/analysis-javascript/lib/target/export/ExpressionStatement.ts +++ b/libraries/analysis-javascript/lib/target/export/ExpressionStatement.ts @@ -103,7 +103,6 @@ export function extractExportsFromRightAssignmentExpression( }); } - console.log(exports); return exports; } diff --git a/libraries/search-javascript/index.ts b/libraries/search-javascript/index.ts index 32ca9ee65..67d61f0b8 100644 --- a/libraries/search-javascript/index.ts +++ b/libraries/search-javascript/index.ts @@ -34,24 +34,20 @@ export * from "./lib/testcase/sampling/JavaScriptRandomSampler"; export * from "./lib/testcase/sampling/JavaScriptTestCaseSampler"; export * from "./lib/testcase/statements/action/ActionStatement"; -export * from "./lib/testcase/statements/action/Getter"; -export * from "./lib/testcase/statements/action/MethodCall"; -export * from "./lib/testcase/statements/action/Setter"; export * from "./lib/testcase/statements/complex/ArrayStatement"; export * from "./lib/testcase/statements/complex/ArrowFunctionStatement"; export * from "./lib/testcase/statements/complex/ObjectStatement"; -export * from "./lib/testcase/statements/primitive/BoolStatement"; -export * from "./lib/testcase/statements/primitive/NullStatement"; -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/primitive/UndefinedStatement"; +export * from "./lib/testcase/statements/literal/BoolStatement"; +export * from "./lib/testcase/statements/literal/NullStatement"; +export * from "./lib/testcase/statements/literal/NumericStatement"; +export * from "./lib/testcase/statements/literal/LiteralStatement"; +export * from "./lib/testcase/statements/literal/StringStatement"; +export * from "./lib/testcase/statements/literal/UndefinedStatement"; export * from "./lib/testcase/statements/action/ConstructorCall"; export * from "./lib/testcase/statements/action/FunctionCall"; -export * from "./lib/testcase/statements/action/ConstantObject"; export * from "./lib/testcase/statements/Statement"; diff --git a/libraries/search-javascript/lib/search/JavaScriptSubject.ts b/libraries/search-javascript/lib/search/JavaScriptSubject.ts index db303fb24..7526ab595 100644 --- a/libraries/search-javascript/lib/search/JavaScriptSubject.ts +++ b/libraries/search-javascript/lib/search/JavaScriptSubject.ts @@ -15,8 +15,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { TargetType } from "@syntest/analysis"; -import { RootContext, SubTarget, Target } from "@syntest/analysis-javascript"; +import { Action, RootContext, Target } from "@syntest/analysis-javascript"; import { ControlFlowGraph, Edge, EdgeType } from "@syntest/cfg"; import { ApproachLevel, @@ -31,15 +30,18 @@ import { BranchDistance } from "../criterion/BranchDistance"; import { JavaScriptTestCase } from "../testcase/JavaScriptTestCase"; export class JavaScriptSubject extends SearchSubject { + protected _actions: Map protected syntaxForgiving: boolean; protected stringAlphabet: string; constructor( target: Target, + actions: Map, rootContext: RootContext, syntaxForgiving: boolean, stringAlphabet: string ) { super(target, rootContext); + this._actions = actions this.syntaxForgiving = syntaxForgiving; this.stringAlphabet = stringAlphabet; @@ -181,19 +183,19 @@ export class JavaScriptSubject extends SearchSubject { return childObjectives; } - getActionableTargets(): SubTarget[] { - return this._target.subTargets.filter((t) => { - return ( - t.type === TargetType.FUNCTION || - t.type === TargetType.CLASS || - t.type === TargetType.METHOD || - t.type === TargetType.OBJECT || - t.type === TargetType.OBJECT_FUNCTION - ); - }); + get actionsMap(): Map { + return this._actions + } + + get actions(): Action[] { + return [...this._actions.values()] + } + + get constructableActions(): Action[] { + return this.actions.filter((x) => x.constructable) } - getActionableTargetsByType(type: TargetType): SubTarget[] { - return this.getActionableTargets().filter((t) => t.type === type); + get functionActions(): Action[] { + return this.actions.filter((x) => x.type === 'function') } } diff --git a/libraries/search-javascript/lib/search/crossover/TreeCrossover.ts b/libraries/search-javascript/lib/search/crossover/TreeCrossover.ts index e953d7710..a2fc714a8 100644 --- a/libraries/search-javascript/lib/search/crossover/TreeCrossover.ts +++ b/libraries/search-javascript/lib/search/crossover/TreeCrossover.ts @@ -21,7 +21,6 @@ import { Crossover } from "@syntest/search"; import { JavaScriptTestCase } from "../../testcase/JavaScriptTestCase"; import { ActionStatement } from "../../testcase/statements/action/ActionStatement"; -import { ConstantObject } from "../../testcase/statements/action/ConstantObject"; import { ConstructorCall } from "../../testcase/statements/action/ConstructorCall"; import { Statement } from "../../testcase/statements/Statement"; @@ -85,29 +84,7 @@ export class TreeCrossover extends Crossover { if ( swapA.child instanceof ConstructorCall && swapB.child instanceof ConstructorCall && - swapA.child.export.id !== swapB.child.export.id - ) { - continue; - } - - if ( - swapA.child instanceof ConstantObject && - !(swapB.child instanceof ConstantObject) - ) { - continue; - } - - if ( - swapB.child instanceof ConstantObject && - !(swapA.child instanceof ConstantObject) - ) { - continue; - } - - if ( - swapA.child instanceof ConstantObject && - swapB.child instanceof ConstantObject && - swapA.child.export.id !== swapB.child.export.id + swapA.child.action.id !== swapB.child.action.id ) { continue; } diff --git a/libraries/search-javascript/lib/testbuilding/ContextBuilder.ts b/libraries/search-javascript/lib/testbuilding/ContextBuilder.ts index 8e4b2badd..c4f81dcfa 100644 --- a/libraries/search-javascript/lib/testbuilding/ContextBuilder.ts +++ b/libraries/search-javascript/lib/testbuilding/ContextBuilder.ts @@ -18,16 +18,15 @@ import * as path from "node:path"; -import { Export } from "@syntest/analysis-javascript"; +import { Action } from "@syntest/analysis-javascript"; import { globalVariables, reservedKeywords, } from "@syntest/ast-visitor-javascript"; import { getLogger, Logger } from "@syntest/logging"; -import { ClassActionStatement } from "../testcase/statements/action/ClassActionStatement"; import { FunctionCall } from "../testcase/statements/action/FunctionCall"; -import { ObjectFunctionCall } from "../testcase/statements/action/ObjectFunctionCall"; +import { ImportStatement } from "../testcase/statements/action/ImportStatement"; import { Statement } from "../testcase/statements/Statement"; type Import = RegularImport | RenamedImport; @@ -35,16 +34,12 @@ type Import = RegularImport | RenamedImport; type RegularImport = { name: string; renamed: false; - module: boolean; - default: boolean; }; type RenamedImport = { name: string; renamed: true; renamedTo: string; - module: boolean; - default: boolean; }; type Require = { @@ -69,6 +64,7 @@ export class ContextBuilder { // Statement -> variableName private statementVariableNameMap: Map; + constructor(targetRootDirectory: string, sourceDirectory: string) { ContextBuilder.LOGGER = getLogger("ContextBuilder"); this.targetRootDirectory = targetRootDirectory; @@ -87,6 +83,10 @@ export class ContextBuilder { } getOrCreateVariableName(statement: Statement): string { + if (statement instanceof ImportStatement) { + return this.getOrCreateImportName(statement.action) + } + if (this.statementVariableNameMap.has(statement)) { return this.statementVariableNameMap.get(statement); } @@ -103,9 +103,7 @@ export class ContextBuilder { : variableName; if ( - statement instanceof ClassActionStatement || - statement instanceof FunctionCall || - statement instanceof ObjectFunctionCall + statement instanceof FunctionCall ) { variableName += "ReturnValue"; } @@ -136,33 +134,31 @@ export class ContextBuilder { return variableName; } - getOrCreateImportName(export_: Export): string { - const import_ = this._addImport(export_); + getOrCreateImportName(action: Action): string { + const import_ = this._addImport(action); return import_.renamed ? import_.renamedTo : import_.name; } - private _addImport(export_: Export): Import { - const path_ = export_.filePath.replace( + protected _addImport(action: Action): Import { + const path_ = action.filePath.replace( path.resolve(this.targetRootDirectory), path.join(this.sourceDirectory, path.basename(this.targetRootDirectory)) ); - const exportedName = export_.renamedTo; + + + const exportedName = action.name; let import_: Import = { - name: exportedName === "default" ? "defaultExport" : exportedName, + name: exportedName === "__root__" ? "defaultExport" : exportedName, renamed: false, - default: export_.default, - module: export_.module, }; let newName: string = exportedName; if (this.imports.has(path_)) { const foundImport = this.imports.get(path_).find((value) => { return ( - value.name === import_.name && - value.default === import_.default && - value.module === import_.module + value.name === import_.name ); }); if (foundImport !== undefined) { @@ -197,9 +193,7 @@ export class ContextBuilder { import_ = { name: exportedName, renamed: true, - renamedTo: newName, - default: export_.default, - module: export_.module, + renamedTo: newName }; } @@ -211,50 +205,13 @@ export class ContextBuilder { return import_; } - // TODO we could gather all the imports of a certain path together into one import - private _getImportString(_path: string, import_: Import): string { - if (import_.module) { - throw new Error("Only non module imports can use import statements"); - } - - // if (import_.renamed) { - // return import_.default - // ? `const ${import_.renamedTo} = require("${_path}";` - // : `const {${import_.name} as ${import_.renamedTo}} = equire("${_path}";`; - // } else { - // return import_.default - // ? `const ${import_.name} = require("${_path}";` - // : `const {${import_.name}} = require("${_path}";`; - // } - - if (import_.renamed) { - return import_.default - ? `import ${import_.renamedTo} from "${_path}";` - : `import {${import_.name} as ${import_.renamedTo}} from "${_path}";`; - } else { - return import_.default - ? `import ${import_.name} from "${_path}";` - : `import {${import_.name}} from "${_path}";`; - } - } - private _getRequireString(_path: string, import_: Import): Require { - if (!import_.module) { - throw new Error("Only module imports can use require statements"); - } - const require: Require = { left: "", - right: `require("${_path}")`, + right: `import("${_path}")`, }; - if (import_.renamed) { - require.left = import_.default - ? import_.renamedTo - : `{${import_.name}: ${import_.renamedTo}}`; - } else { - require.left = import_.default ? import_.name : `{${import_.name}}`; - } + require.left = import_.renamed && import_.renamedTo ? import_.renamedTo : import_.name; return require; } @@ -266,11 +223,7 @@ export class ContextBuilder { for (const [path_, imports_] of this.imports.entries()) { // TODO remove unused imports for (const import_ of imports_) { - if (import_.module) { - requires.push(this._getRequireString(path_, import_)); - } else { - imports.push(this._getImportString(path_, import_)); - } + requires.push(this._getRequireString(path_, import_)); } } diff --git a/libraries/search-javascript/lib/testbuilding/JavaScriptDecoder.ts b/libraries/search-javascript/lib/testbuilding/JavaScriptDecoder.ts index 998605a9e..5b62cd9da 100644 --- a/libraries/search-javascript/lib/testbuilding/JavaScriptDecoder.ts +++ b/libraries/search-javascript/lib/testbuilding/JavaScriptDecoder.ts @@ -20,9 +20,8 @@ import { Decoder } from "@syntest/search"; import { JavaScriptTestCase } from "../testcase/JavaScriptTestCase"; import { ActionStatement } from "../testcase/statements/action/ActionStatement"; -import { ClassActionStatement } from "../testcase/statements/action/ClassActionStatement"; import { FunctionCall } from "../testcase/statements/action/FunctionCall"; -import { ObjectFunctionCall } from "../testcase/statements/action/ObjectFunctionCall"; +import { ImportStatement } from "../testcase/statements/action/ImportStatement"; import { Decoding } from "../testcase/statements/Statement"; import { assertionFunction } from "./assertionFunctionTemplate"; @@ -125,7 +124,6 @@ export class JavaScriptDecoder implements Decoder { const lines = [ "// Imports", - "require = require('esm')(module)", ...imports, gatherAssertionData ? assertionFunction : "", `describe('SynTest Test Suite', function() {`, @@ -167,23 +165,27 @@ export class JavaScriptDecoder implements Decoder { } for (const [index, value] of decodings.entries()) { + if (value.reference instanceof ImportStatement) { + // do not include import statements in the test lines + continue + } + + const variableName = value.variableName const asString = value.decoded; - if (testLines.includes(asString)) { + const fullString = `const ${variableName} = ${asString}` + if (testLines.includes(fullString)) { // skip repeated statements continue; } - testLines.push(asString); + testLines.push(fullString); if (gatherAssertionData) { // add log per statement - const variableName = context.getOrCreateVariableName(value.reference); testLines.push(`count = ${index + 1};`); if ( - value.reference instanceof FunctionCall || - value.reference instanceof ObjectFunctionCall || - value.reference instanceof ClassActionStatement + value.reference instanceof FunctionCall ) { testLines.push( `addAssertion('${testCase.id}', '${variableName}', ${variableName})` @@ -256,7 +258,7 @@ export class JavaScriptDecoder implements Decoder { assertions.push( `await expect((async () => {`, - `\t${errorDecoding.decoded.split(" = ")[1]}`, + `\t${errorDecoding.decoded}`, `})()).to.be.rejectedWith("${value}")` ); } diff --git a/libraries/search-javascript/lib/testcase/JavaScriptTestCase.ts b/libraries/search-javascript/lib/testcase/JavaScriptTestCase.ts index f423be009..e68a8f8bb 100644 --- a/libraries/search-javascript/lib/testcase/JavaScriptTestCase.ts +++ b/libraries/search-javascript/lib/testcase/JavaScriptTestCase.ts @@ -65,7 +65,7 @@ export class JavaScriptTestCase extends Encoding { 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()); + roots.splice(index, 0, sampler.sampleFunctionCall(0)); } else if (choice < 0.66) { // 33% chance to delete the root const index = prng.nextInt(0, roots.length - 1); @@ -79,7 +79,7 @@ export class JavaScriptTestCase extends Encoding { 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()); + roots.splice(index, 0, sampler.sampleFunctionCall(0)); } else { // 50% chance to just mutate the root const index = prng.nextInt(0, roots.length - 1); diff --git a/libraries/search-javascript/lib/testcase/StatementPool.ts b/libraries/search-javascript/lib/testcase/StatementPool.ts index 423bad572..2ea51ea53 100644 --- a/libraries/search-javascript/lib/testcase/StatementPool.ts +++ b/libraries/search-javascript/lib/testcase/StatementPool.ts @@ -15,14 +15,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +import { Action } from "@syntest/analysis-javascript"; import { prng } from "@syntest/prng"; import { ActionStatement } from "./statements/action/ActionStatement"; -import { ClassActionStatement } from "./statements/action/ClassActionStatement"; -import { ConstantObject } from "./statements/action/ConstantObject"; import { ConstructorCall } from "./statements/action/ConstructorCall"; import { FunctionCall } from "./statements/action/FunctionCall"; -import { ObjectFunctionCall } from "./statements/action/ObjectFunctionCall"; import { Statement } from "./statements/Statement"; export class StatementPool { @@ -31,12 +29,10 @@ export class StatementPool { // this is a bit out of scope for this class but otherwise we have to walk the tree multiple times // we can solve this by making a singular tree walker class with visitors private constructors: ConstructorCall[]; - private objects: ConstantObject[]; constructor(roots: ActionStatement[]) { this.pool = new Map(); this.constructors = []; - this.objects = []; this._fillGenePool(roots); } @@ -50,9 +46,9 @@ export class StatementPool { return prng.pickOne(statements); } - public getRandomConstructor(exportId?: string): ConstructorCall { - const options = exportId - ? this.constructors.filter((o) => exportId === o.export.id) + public getRandomConstructor(action?: Action): ConstructorCall { + const options = action + ? this.constructors.filter((o) => action.id === o.action.id) : this.constructors; if (options.length === 0) { @@ -61,16 +57,6 @@ export class StatementPool { return prng.pickOne(options); } - public getRandomConstantObject(exportId: string): ConstantObject { - const options = exportId - ? this.objects.filter((o) => exportId === o.export.id) - : this.objects; - if (options.length === 0) { - return undefined; - } - return prng.pickOne(options); - } - private _fillGenePool(roots: ActionStatement[]) { for (const action of roots) { const queue: Statement[] = [action]; @@ -83,20 +69,10 @@ export class StatementPool { } // use type enum for primitives and arrays - let type: string = statement.ownType; + const type: string = statement.returnType; - if (statement instanceof ConstantObject) { - // use export identifier - type = statement.export.id; - this.objects.push(statement); - } else if (statement instanceof ConstructorCall) { - // use export identifier - type = statement.export.id; - this.constructors.push(statement); - } else if ( - statement instanceof FunctionCall || - statement instanceof ClassActionStatement || - statement instanceof ObjectFunctionCall + if ( + statement instanceof FunctionCall ) { // TODO use return type // type = statement. diff --git a/libraries/search-javascript/lib/testcase/execution/ExecutionInformationIntegrator.ts b/libraries/search-javascript/lib/testcase/execution/ExecutionInformationIntegrator.ts index 9af9b8bb7..ed25dc61c 100644 --- a/libraries/search-javascript/lib/testcase/execution/ExecutionInformationIntegrator.ts +++ b/libraries/search-javascript/lib/testcase/execution/ExecutionInformationIntegrator.ts @@ -17,6 +17,7 @@ */ import { TypeModel } from "@syntest/analysis-javascript"; +import { getLogger, Logger } from "@syntest/logging"; import Mocha = require("mocha"); import { JavaScriptTestCase } from "../JavaScriptTestCase"; @@ -25,9 +26,11 @@ import { Statement } from "../statements/Statement"; import { Test } from "./TestExecutor"; export class ExecutionInformationIntegrator { + protected static LOGGER: Logger private _typeModel: TypeModel; constructor(typeModel: TypeModel) { + ExecutionInformationIntegrator.LOGGER = getLogger(ExecutionInformationIntegrator.name) this._typeModel = typeModel; } @@ -46,12 +49,13 @@ export class ExecutionInformationIntegrator { if ( testResult.error && testResult.error.message && - testResult.error.message.includes(child.name) + testResult.error.message.includes(child.name) && (child.name !== 'constructor') ) { + ExecutionInformationIntegrator.LOGGER.info(`Adding execution score to '${child.name}' with id '${child.variableIdentifier}'`) this._typeModel.addExecutionScore( child.variableIdentifier, child.typeIdentifier, - child.ownType + child.type ); } queue.push(child); diff --git a/libraries/search-javascript/lib/testcase/sampling/JavaScriptRandomSampler.ts b/libraries/search-javascript/lib/testcase/sampling/JavaScriptRandomSampler.ts index d0c6f921d..093db9c1b 100644 --- a/libraries/search-javascript/lib/testcase/sampling/JavaScriptRandomSampler.ts +++ b/libraries/search-javascript/lib/testcase/sampling/JavaScriptRandomSampler.ts @@ -16,44 +16,37 @@ * limitations under the License. */ -import { TargetType } from "@syntest/analysis"; import { - ClassTarget, + Action, ConstantPoolManager, DiscoveredObjectKind, - FunctionTarget, - isExported, - MethodTarget, - ObjectFunctionTarget, - ObjectTarget, } from "@syntest/analysis-javascript"; +import { getLogger, Logger } from "@syntest/logging"; import { prng } from "@syntest/prng"; import { JavaScriptSubject } from "../../search/JavaScriptSubject"; import { JavaScriptTestCase } from "../JavaScriptTestCase"; import { StatementPool } from "../StatementPool"; import { ActionStatement } from "../statements/action/ActionStatement"; -import { ConstantObject } from "../statements/action/ConstantObject"; import { ConstructorCall } from "../statements/action/ConstructorCall"; import { FunctionCall } from "../statements/action/FunctionCall"; -import { Getter } from "../statements/action/Getter"; -import { MethodCall } from "../statements/action/MethodCall"; -import { ObjectFunctionCall } from "../statements/action/ObjectFunctionCall"; -import { Setter } from "../statements/action/Setter"; +import { ImportStatement } from "../statements/action/ImportStatement"; +import { MemberStatement } from "../statements/action/MemberStatement"; import { ArrayStatement } from "../statements/complex/ArrayStatement"; import { ArrowFunctionStatement } from "../statements/complex/ArrowFunctionStatement"; import { ObjectStatement } from "../statements/complex/ObjectStatement"; -import { BoolStatement } from "../statements/primitive/BoolStatement"; -import { IntegerStatement } from "../statements/primitive/IntegerStatement"; -import { NullStatement } from "../statements/primitive/NullStatement"; -import { NumericStatement } from "../statements/primitive/NumericStatement"; -import { StringStatement } from "../statements/primitive/StringStatement"; -import { UndefinedStatement } from "../statements/primitive/UndefinedStatement"; +import { BoolStatement } from "../statements/literal/BoolStatement"; +import { IntegerStatement } from "../statements/literal/IntegerStatement"; +import { NullStatement } from "../statements/literal/NullStatement"; +import { NumericStatement } from "../statements/literal/NumericStatement"; +import { StringStatement } from "../statements/literal/StringStatement"; +import { UndefinedStatement } from "../statements/literal/UndefinedStatement"; import { Statement } from "../statements/Statement"; import { JavaScriptTestCaseSampler } from "./JavaScriptTestCaseSampler"; export class JavaScriptRandomSampler extends JavaScriptTestCaseSampler { + private static LOGGER: Logger constructor( subject: JavaScriptSubject, constantPoolManager: ConstantPoolManager, @@ -90,9 +83,13 @@ export class JavaScriptRandomSampler extends JavaScriptTestCaseSampler { deltaMutationProbability, exploreIllegalValues ); + + JavaScriptRandomSampler.LOGGER = getLogger(JavaScriptRandomSampler.name) } sample(): JavaScriptTestCase { + JavaScriptRandomSampler.LOGGER.debug('Sampling Encoding') + console.log("SAMPLING ENCODING") const roots: ActionStatement[] = []; for ( @@ -101,365 +98,220 @@ export class JavaScriptRandomSampler extends JavaScriptTestCaseSampler { index++ ) { this.statementPool = new StatementPool(roots); - roots.push(this.sampleRoot()); + roots.push(this.sampleFunctionCall(0)); } this.statementPool = undefined; return new JavaScriptTestCase(roots); } - sampleRoot(): ActionStatement { - const targets = (this._subject).getActionableTargets(); - if (this.statementPoolEnabled) { - const constructor_ = this.statementPool.getRandomConstructor(); + override sampleParentAction(depth: number, action: Action): ActionStatement { + const actions = this.rootContext + .getAllActions() + .get(action.filePath) + const parentAction = actions.get(action.parentId) + return parentAction ? this.sampleMemberStatement(depth, parentAction, action.name) : this.sampleImportStatement(depth, action); + } - if (constructor_ && prng.nextBoolean(this.statementPoolProbability)) { - // TODO ignoring getters and setters for now - const targets = this.rootContext.getSubTargets( - constructor_.typeIdentifier.split(":")[0] - ); - const methods = ( - targets.filter( - (target) => - target.type === TargetType.METHOD && - (target).methodType === "method" && - (target).classId === constructor_.classIdentifier - ) - ); - if (methods.length > 0) { - const method = prng.pickOne(methods); - - const type_ = this.rootContext - .getTypeModel() - .getObjectDescription(method.typeId); - - const arguments_: Statement[] = - this.methodCallGenerator.sampleArguments(0, type_); - - return new MethodCall( - method.id, - method.typeId, - method.name, - prng.uniqueId(), - arguments_, - constructor_ - ); - } - } - } + sampleSpecificFunctionCall(depth: number, action: string | Action): FunctionCall { + JavaScriptRandomSampler.LOGGER.debug('Sampling Function') - const action = prng.pickOne( - targets.filter( - (target) => - (target.type === TargetType.FUNCTION && isExported(target)) || - (target.type === TargetType.CLASS && isExported(target)) || - (target.type === TargetType.OBJECT && isExported(target)) || - (target.type === TargetType.METHOD && - (target).methodType !== "constructor" && - isExported( - targets.find( - (classTarget) => - classTarget.id === (target).classId - ) - )) || // check whether parent class is exported - (target.type === TargetType.OBJECT_FUNCTION && - isExported( - targets.find( - (objectTarget) => - objectTarget.id === (target).objectId - ) - )) // check whether parent object is exported - ) - ); + if (typeof action === 'string') { + const actions = this.rootContext + .getAllActions() + .get(action.split(':')[0]) - switch (action.type) { - case TargetType.FUNCTION: { - return this.sampleFunctionCall(0); - } - case TargetType.CLASS: { - return this.sampleConstructorCall(0); - } - case TargetType.OBJECT: { - return this.sampleConstantObject(0); - } - case TargetType.METHOD: { - return this.sampleClassAction(0); - } - default: { - return this.sampleObjectFunctionCall(0); - } + action = actions.get(action) + // TODO what if not existing } - } - sampleFunctionCall(depth: number): FunctionCall { - // get a random function - const function_ = ( - prng.pickOne( - (this._subject) - .getActionableTargetsByType(TargetType.FUNCTION) - .filter((target) => isExported(target)) - ) - ); + const id = action.id + + const type_ = this.rootContext + .getTypeModel() + .getObjectDescription(id); - return this.functionCallGenerator.generate( - depth, - function_.id, - function_.typeId, - function_.id, - function_.name, - this.statementPool - ); - } + const arguments_: Statement[] = this.sampleArguments(depth, type_); - private _getClass(id?: string) { - if (id) { - const result = ( - (this._subject) - .getActionableTargetsByType(TargetType.CLASS) - .find((target) => (target).id === id) - ); - if (!result) { - throw new Error("missing class with id: " + id); - } else if (!isExported(result)) { - throw new Error("class with id: " + id + "is not exported"); - } - return result; - } + const parent: Statement = this.sampleParentAction(depth + 1, action) - // random - return ( - prng.pickOne( - (this._subject) - .getActionableTargetsByType(TargetType.CLASS) - .filter((target) => isExported(target)) - ) + return new FunctionCall( + id, + id, + action.name, + prng.uniqueId(), + action, + arguments_, + parent ); } - sampleConstructorCall(depth: number, classId?: string): ConstructorCall { - // get a random class - const class_ = this._getClass(classId); - - // get the constructor of the class - const constructor_ = (this._subject) - .getActionableTargetsByType(TargetType.METHOD) - .filter( - (method) => - (method).classId === class_.id && - (method).methodType === "constructor" - ); - - if (constructor_.length > 1) { - throw new Error("Multiple constructors found for class"); - } + sampleFunctionCall(depth: number): FunctionCall { + // TODO statement stuff + // if (this.statementPoolEnabled) { + // const constructor_ = this.statementPool.getRandomConstructor(); + + // if (constructor_ && prng.nextBoolean(this.statementPoolProbability)) { + // // TODO ignoring getters and setters for now + // const actions = Object.values( + // this.rootContext + // .getAllActions() + // .get(constructor_.typeIdentifier.split(":")[0]) + // .get(constructor_.action.id) + // .children + // ).filter((x) => x.name === 'constructor') + + // if (actions.length > 0) { + // const action = prng.pickOne(actions); + + // const type_ = this.rootContext + // .getTypeModel() + // .getObjectDescription(action.id); + + // const arguments_: Statement[] = + // this.sampleArguments(0, type_); + + // return new FunctionCall( + // action.id, + // action.id, + // action.name, + // prng.uniqueId(), + // arguments_, + // action, + // constructor_ + // ); + // } + // } + // } - if (constructor_.length === 0) { - // default constructor no args - const export_ = [...this.rootContext.getAllExports().values()] - .flat() - .find((export_) => export_.id === class_.id); + // get a random function + const actions = (this._subject).functionActions + const action = prng.pickOne(actions) - return new ConstructorCall( - class_.id, - class_.typeId, - class_.id, - class_.name, - prng.uniqueId(), - [], - export_ - ); - } else { - const action = constructor_[0]; - return this.constructorCallGenerator.generate( - depth, - action.id, - (action).typeId, - class_.id, - class_.name, - this.statementPool - ); + if (action.name === 'constructor') { + return this.sampleSpecificConstructorCall(depth, action) } - } - override sampleClassAction(depth: number): MethodCall | Getter | Setter { - const targets = (this._subject).getActionableTargets(); - - const methods = (this._subject) - .getActionableTargetsByType(TargetType.METHOD) - .filter( - (method) => - (method).methodType !== "constructor" && - isExported( - targets.find( - (classTarget) => classTarget.id === (method).classId - ) - ) - ); + return this.sampleSpecificFunctionCall(depth, action) + } - const randomMethod = prng.pickOne(methods); - switch (randomMethod.methodType) { - case "method": { - return this.sampleMethodCall(depth); - } - case "get": { - return this.sampleGetter(depth); - } - case "set": { - return this.sampleSetter(depth); - } - case "constructor": { - throw new Error("invalid path"); - } - // No default + sampleSpecificConstructorCall(depth: number, action: string | Action): ConstructorCall { + // TODO could make this more efficient by having either an id or action as argument + + // TODO statement pool + // if (this.statementPoolEnabled) { + // const statementFromPool = + // this.statementPool.getRandomConstructor(action); + + // if ( + // statementFromPool && + // prng.nextBoolean(this.statementPoolProbability) + // ) { + // return statementFromPool; + // } + // } + + if (typeof action === 'string') { + const actions = this.rootContext + .getAllActions() + .get(action.split(':')[0]) + + action = actions.get(action) + // TODO what if not existing } - } - override sampleMethodCall(depth: number): MethodCall { - const targets = (this._subject).getActionableTargets(); - - const methods = (this._subject) - .getActionableTargetsByType(TargetType.METHOD) - .filter((method) => (method).methodType === "method") - .filter((target) => - isExported( - targets.find( - (objectTarget) => objectTarget.id === (target).classId - ) - ) - ); + const id = action.id - const method = prng.pickOne(methods); - const class_ = this._getClass(method.classId); + const type_ = this.rootContext + .getTypeModel() + .getObjectDescription(id); - return this.methodCallGenerator.generate( - depth, - method.id, - method.typeId, - class_.id, - method.name, - this.statementPool - ); - } + // TODO what if not existing - sampleGetter(depth: number): Getter { - const targets = (this._subject).getActionableTargets(); - - const methods = (this._subject) - .getActionableTargetsByType(TargetType.METHOD) - .filter((method) => (method).methodType === "get") - .filter((target) => - isExported( - targets.find( - (objectTarget) => objectTarget.id === (target).classId - ) - ) - ); + const arguments_: Statement[] = this.sampleArguments(depth, type_); - const method = prng.pickOne(methods); - const class_ = this._getClass(method.classId); + const parent = this.sampleParentAction(depth + 1, action) - return this.getterGenerator.generate( - depth, - method.id, - method.id, - class_.id, - method.name, - this.statementPool + return new ConstructorCall( + id, + id, + action.name, + prng.uniqueId(), + action, + arguments_, + parent ); } - sampleSetter(depth: number): Setter { - const targets = (this._subject).getActionableTargets(); - - const methods = (this._subject) - .getActionableTargetsByType(TargetType.METHOD) - .filter((method) => (method).methodType === "set") - .filter((target) => - isExported( - targets.find( - (objectTarget) => objectTarget.id === (target).classId - ) - ) - ); - - const method = prng.pickOne(methods); - const class_ = this._getClass(method.classId); + sampleConstructorCall(depth: number): ConstructorCall { + // get a random class + const classActions = (this._subject).constructableActions + const classAction = prng.pickOne(classActions) + const constructorAction = classAction.hasOwnProperty('constructor') ? classAction.children['constructor'] : undefined - return this.setterGenerator.generate( - depth, - method.id, - method.typeId, - class_.id, - method.name, - this.statementPool - ); + if (constructorAction) { + return this.sampleSpecificConstructorCall( + depth, + constructorAction.id + ); + } else { + const parent = this.sampleParentAction(depth + 1, classAction) + // default constructor no args + return new ConstructorCall( + classAction.id, + classAction.id, + classAction.name, + prng.uniqueId(), + classAction, + [], + parent + ); + } } - private _getObject(id?: string) { - if (id) { - const result = ( - (this._subject) - .getActionableTargetsByType(TargetType.OBJECT) - .find((target) => (target).id === id) - ); - if (!result) { - throw new Error("missing object with id: " + id); - } else if (!isExported(result)) { - throw new Error("object with id: " + id + " is not exported"); + sampleMemberStatement( + depth: number, + parentAction: Action, + key: string + ): MemberStatement { + let parent: Statement + if (parentAction.type === 'function') { + parent = this.sampleSpecificFunctionCall(depth + 1, parentAction) + } else if (parentAction.type === 'object') { + if (parentAction.constructable) { + parent = this.sampleSpecificConstructorCall(depth + 1, parentAction) + } else { + // get higher parent + parent = this.sampleParentAction(depth + 1, parentAction) } - return result; + } else { + // TODO + throw new Error("not supported yet") } - // random - return ( - prng.pickOne( - (this._subject) - .getActionableTargetsByType(TargetType.OBJECT) - .filter((target) => isExported(target)) - ) - ); - } - - sampleConstantObject(depth: number, objectId?: string): ConstantObject { - // get a random object - const object_ = this._getObject(objectId); - - return this.constantObjectGenerator.generate( - depth, - object_.id, - object_.typeId, - object_.id, - object_.name, - this.statementPool - ); + return new MemberStatement( + '', + '', + key, + prng.uniqueId(), + parentAction, + parent, + key + ) } - sampleObjectFunctionCall(depth: number): ObjectFunctionCall { - const targets = (this._subject).getActionableTargets(); - - const functions = (this._subject) - .getActionableTargetsByType(TargetType.OBJECT_FUNCTION) - .filter((target) => - isExported( - targets.find( - (objectTarget) => - objectTarget.id === (target).objectId - ) - ) - ); - - const randomFunction = prng.pickOne(functions); - const object_ = this._getObject(randomFunction.objectId); - - return this.objectFunctionCallGenerator.generate( - depth, - randomFunction.id, - randomFunction.typeId, - object_.id, - randomFunction.name, - this.statementPool - ); + sampleImportStatement( + depth: number, + action: Action + ): ImportStatement { + return new ImportStatement( + "", + "", + "import", + prng.uniqueId(), + action + ) } // arguments @@ -622,69 +474,28 @@ export class JavaScriptRandomSampler extends JavaScriptTestCaseSampler { switch (typeFromTypePool.kind) { case DiscoveredObjectKind.CLASS: { // find constructor of class - const targets = this.rootContext.getSubTargets( - typeFromTypePool.id.split(":")[0] - ); - const constructor_ = ( - targets.find( - (target) => - target.type === TargetType.METHOD && - (target).methodType === "constructor" && - (target).classId === typeFromTypePool.id - ) - ); - - if (constructor_) { - return this.constructorCallGenerator.generate( - depth, - id, // variable id - constructor_.typeId, // constructor call id - typeFromTypePool.id, // class export id - name, - this.statementPool - ); - } - - return this.constructorCallGenerator.generate( + return this.sampleSpecificConstructorCall( depth, - id, // variable id - typeFromTypePool.id, // constructor call id - typeFromTypePool.id, // class export id - name, - this.statementPool + typeFromTypePool.id ); } case DiscoveredObjectKind.FUNCTION: { - return this.functionCallGenerator.generate( + return this.sampleSpecificFunctionCall( depth, - id, - typeFromTypePool.id, - typeFromTypePool.id, - name, - this.statementPool + typeFromTypePool.id ); } case DiscoveredObjectKind.INTERFACE: { // TODO - return this.constructorCallGenerator.generate( + return this.sampleSpecificConstructorCall( depth, - id, - typeFromTypePool.id, - typeFromTypePool.id, - name, - this.statementPool - ); - } - case DiscoveredObjectKind.OBJECT: { - return this.constantObjectGenerator.generate( - depth, - id, - typeFromTypePool.id, - typeFromTypePool.id, - name, - this.statementPool + typeFromTypePool.id ); } + case DiscoveredObjectKind.OBJECT: + // uhhh + // TODO + // No default } } diff --git a/libraries/search-javascript/lib/testcase/sampling/JavaScriptTestCaseSampler.ts b/libraries/search-javascript/lib/testcase/sampling/JavaScriptTestCaseSampler.ts index 3082386ee..ea4734d06 100644 --- a/libraries/search-javascript/lib/testcase/sampling/JavaScriptTestCaseSampler.ts +++ b/libraries/search-javascript/lib/testcase/sampling/JavaScriptTestCaseSampler.ts @@ -16,43 +16,31 @@ * limitations under the License. */ -import { ConstantPoolManager, RootContext } from "@syntest/analysis-javascript"; +import { Action, ConstantPoolManager, ObjectType, RootContext } from "@syntest/analysis-javascript"; +import { prng } from "@syntest/prng"; import { EncodingSampler } from "@syntest/search"; import { JavaScriptSubject } from "../../search/JavaScriptSubject"; import { JavaScriptTestCase } from "../JavaScriptTestCase"; import { StatementPool } from "../StatementPool"; import { ActionStatement } from "../statements/action/ActionStatement"; -import { ConstantObject } from "../statements/action/ConstantObject"; import { ConstructorCall } from "../statements/action/ConstructorCall"; import { FunctionCall } from "../statements/action/FunctionCall"; -import { Getter } from "../statements/action/Getter"; -import { MethodCall } from "../statements/action/MethodCall"; -import { ObjectFunctionCall } from "../statements/action/ObjectFunctionCall"; -import { Setter } from "../statements/action/Setter"; +import { ImportStatement } from "../statements/action/ImportStatement"; +import { MemberStatement } from "../statements/action/MemberStatement"; import { ArrayStatement } from "../statements/complex/ArrayStatement"; import { ArrowFunctionStatement } from "../statements/complex/ArrowFunctionStatement"; import { ObjectStatement } from "../statements/complex/ObjectStatement"; -import { BoolStatement } from "../statements/primitive/BoolStatement"; -import { IntegerStatement } from "../statements/primitive/IntegerStatement"; -import { NullStatement } from "../statements/primitive/NullStatement"; -import { NumericStatement } from "../statements/primitive/NumericStatement"; -import { StringStatement } from "../statements/primitive/StringStatement"; -import { UndefinedStatement } from "../statements/primitive/UndefinedStatement"; +import { BoolStatement } from "../statements/literal/BoolStatement"; +import { IntegerStatement } from "../statements/literal/IntegerStatement"; +import { NullStatement } from "../statements/literal/NullStatement"; +import { NumericStatement } from "../statements/literal/NumericStatement"; +import { StringStatement } from "../statements/literal/StringStatement"; +import { UndefinedStatement } from "../statements/literal/UndefinedStatement"; import { Statement } from "../statements/Statement"; -import { ConstantObjectGenerator } from "./generators/action/ConstantObjectGenerator"; -import { ConstructorCallGenerator } from "./generators/action/ConstructorCallGenerator"; -import { FunctionCallGenerator } from "./generators/action/FunctionCallGenerator"; -import { GetterGenerator } from "./generators/action/GetterGenerator"; -import { MethodCallGenerator } from "./generators/action/MethodCallGenerator"; -import { ObjectFunctionCallGenerator } from "./generators/action/ObjectFunctionCallGenerator"; -import { SetterGenerator } from "./generators/action/SetterGenerator"; - /** * JavaScriptRandomSampler class - * - * @author Dimitri Stallenberg */ export abstract class JavaScriptTestCaseSampler extends EncodingSampler { private _rootContext: RootContext; @@ -82,16 +70,6 @@ export abstract class JavaScriptTestCaseSampler extends EncodingSampler { - protected _sampler: JavaScriptTestCaseSampler; - protected _rootContext: RootContext; - - protected _statementPoolEnabled: boolean; - protected _statementPoolProbability: number; - - constructor( - sampler: JavaScriptTestCaseSampler, - rootContext: RootContext, - statementPoolEnabled: boolean, - statementPoolProbability: number - ) { - this._sampler = sampler; - this._rootContext = rootContext; - this._statementPoolEnabled = statementPoolEnabled; - this._statementPoolProbability = statementPoolProbability; - } - - abstract generate( - depth: number, - variableIdentifier: string, - typeIdentifier: string, - exportIdentifier: string, - name: string, - statementPool: StatementPool - ): S; - - get sampler() { - return this._sampler; - } - - get rootContext() { - return this._rootContext; - } - - get statementPoolEnabled() { - return this._statementPoolEnabled; - } - - get statementPoolProbability() { - return this._statementPoolProbability; - } -} diff --git a/libraries/search-javascript/lib/testcase/sampling/generators/action/CallGenerator.ts b/libraries/search-javascript/lib/testcase/sampling/generators/action/CallGenerator.ts deleted file mode 100644 index cd0bc8235..000000000 --- a/libraries/search-javascript/lib/testcase/sampling/generators/action/CallGenerator.ts +++ /dev/null @@ -1,58 +0,0 @@ -/* - * 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 { ObjectType } from "@syntest/analysis-javascript"; -import { prng } from "@syntest/prng"; - -import { Statement } from "../../../statements/Statement"; -import { Generator } from "../Generator"; - -export abstract class CallGenerator extends Generator { - sampleArguments(depth: number, type_: ObjectType): Statement[] { - const arguments_: Statement[] = []; - - for (const [index, parameterId] of type_.parameters.entries()) { - const name = type_.parameterNames.get(index); - arguments_[index] = this.sampler.sampleArgument( - depth + 1, - parameterId, - name - ); - } - - // if some params are missing, fill them with fake params - const parameterIds = [...type_.parameters.values()]; - for (let index = 0; index < arguments_.length; index++) { - if (!arguments_[index]) { - arguments_[index] = this.sampler.sampleArgument( - depth + 1, - prng.pickOne(parameterIds), - String(index) - ); - } - } - - for (let index = 0; index < 10; index++) { - if (prng.nextBoolean(0.05)) { - // TODO make this a config parameter - arguments_.push(this.sampler.sampleArgument(depth + 1, "anon", "anon")); - } - } - - return arguments_; - } -} diff --git a/libraries/search-javascript/lib/testcase/sampling/generators/action/ConstantObjectGenerator.ts b/libraries/search-javascript/lib/testcase/sampling/generators/action/ConstantObjectGenerator.ts deleted file mode 100644 index 0d0a7d691..000000000 --- a/libraries/search-javascript/lib/testcase/sampling/generators/action/ConstantObjectGenerator.ts +++ /dev/null @@ -1,58 +0,0 @@ -/* - * 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 { prng } from "@syntest/prng"; - -import { StatementPool } from "../../../StatementPool"; -import { ConstantObject } from "../../../statements/action/ConstantObject"; - -import { CallGenerator } from "./CallGenerator"; - -export class ConstantObjectGenerator extends CallGenerator { - override generate( - depth: number, - variableIdentifier: string, - typeIdentifier: string, - exportIdentifier: string, - name: string, - statementPool: StatementPool - ): ConstantObject { - const export_ = [...this.rootContext.getAllExports().values()] - .flat() - .find((export_) => export_.id === exportIdentifier); - - if (this.statementPoolEnabled) { - const statementFromPool = - statementPool.getRandomConstantObject(exportIdentifier); - - if ( - statementFromPool && - prng.nextBoolean(this.statementPoolProbability) - ) { - return statementFromPool; - } - } - - return new ConstantObject( - variableIdentifier, - typeIdentifier, - name, - prng.uniqueId(), - export_ - ); - } -} diff --git a/libraries/search-javascript/lib/testcase/sampling/generators/action/ConstructorCallGenerator.ts b/libraries/search-javascript/lib/testcase/sampling/generators/action/ConstructorCallGenerator.ts deleted file mode 100644 index d8174f7f2..000000000 --- a/libraries/search-javascript/lib/testcase/sampling/generators/action/ConstructorCallGenerator.ts +++ /dev/null @@ -1,67 +0,0 @@ -/* - * 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 { prng } from "@syntest/prng"; - -import { StatementPool } from "../../../StatementPool"; -import { ConstructorCall } from "../../../statements/action/ConstructorCall"; -import { Statement } from "../../../statements/Statement"; - -import { CallGenerator } from "./CallGenerator"; - -export class ConstructorCallGenerator extends CallGenerator { - override generate( - depth: number, - variableIdentifier: string, - typeIdentifier: string, - exportIdentifier: string, - name: string, - statementPool: StatementPool - ): ConstructorCall { - const export_ = [...this.rootContext.getAllExports().values()] - .flat() - .find((export_) => export_.id === exportIdentifier); - - if (this.statementPoolEnabled) { - const statementFromPool = - statementPool.getRandomConstructor(exportIdentifier); - - if ( - statementFromPool && - prng.nextBoolean(this.statementPoolProbability) - ) { - return statementFromPool; - } - } - - const type_ = this.rootContext - .getTypeModel() - .getObjectDescription(typeIdentifier); - - const arguments_: Statement[] = this.sampleArguments(depth, type_); - - return new ConstructorCall( - variableIdentifier, - typeIdentifier, - exportIdentifier, - name, - prng.uniqueId(), - arguments_, - export_ - ); - } -} diff --git a/libraries/search-javascript/lib/testcase/sampling/generators/action/FunctionCallGenerator.ts b/libraries/search-javascript/lib/testcase/sampling/generators/action/FunctionCallGenerator.ts deleted file mode 100644 index 405a94913..000000000 --- a/libraries/search-javascript/lib/testcase/sampling/generators/action/FunctionCallGenerator.ts +++ /dev/null @@ -1,54 +0,0 @@ -/* - * 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 { prng } from "@syntest/prng"; - -import { StatementPool } from "../../../StatementPool"; -import { FunctionCall } from "../../../statements/action/FunctionCall"; -import { Statement } from "../../../statements/Statement"; - -import { CallGenerator } from "./CallGenerator"; - -export class FunctionCallGenerator extends CallGenerator { - override generate( - depth: number, - variableIdentifier: string, - typeIdentifier: string, - exportIdentifier: string, - name: string, - _statementPool: StatementPool - ): FunctionCall { - const type_ = this.rootContext - .getTypeModel() - .getObjectDescription(typeIdentifier); - - const arguments_: Statement[] = this.sampleArguments(depth, type_); - - const export_ = [...this.rootContext.getAllExports().values()] - .flat() - .find((export_) => export_.id === exportIdentifier); - - return new FunctionCall( - variableIdentifier, - typeIdentifier, - name, - prng.uniqueId(), - arguments_, - export_ - ); - } -} diff --git a/libraries/search-javascript/lib/testcase/sampling/generators/action/MethodCallGenerator.ts b/libraries/search-javascript/lib/testcase/sampling/generators/action/MethodCallGenerator.ts deleted file mode 100644 index 15403e612..000000000 --- a/libraries/search-javascript/lib/testcase/sampling/generators/action/MethodCallGenerator.ts +++ /dev/null @@ -1,55 +0,0 @@ -/* - * 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 { prng } from "@syntest/prng"; - -import { StatementPool } from "../../../StatementPool"; -import { MethodCall } from "../../../statements/action/MethodCall"; -import { Statement } from "../../../statements/Statement"; - -import { CallGenerator } from "./CallGenerator"; - -export class MethodCallGenerator extends CallGenerator { - override generate( - depth: number, - variableIdentifier: string, - typeIdentifier: string, - exportIdentifier: string, - name: string, - _statementPool: StatementPool - ): MethodCall { - const type_ = this.rootContext - .getTypeModel() - .getObjectDescription(typeIdentifier); - - const arguments_: Statement[] = this.sampleArguments(depth, type_); - - const constructor_ = this.sampler.sampleConstructorCall( - depth + 1, - exportIdentifier - ); - - return new MethodCall( - variableIdentifier, - typeIdentifier, - name, - prng.uniqueId(), - arguments_, - constructor_ - ); - } -} diff --git a/libraries/search-javascript/lib/testcase/sampling/generators/action/ObjectFunctionCallGenerator.ts b/libraries/search-javascript/lib/testcase/sampling/generators/action/ObjectFunctionCallGenerator.ts deleted file mode 100644 index d92680ab4..000000000 --- a/libraries/search-javascript/lib/testcase/sampling/generators/action/ObjectFunctionCallGenerator.ts +++ /dev/null @@ -1,55 +0,0 @@ -/* - * 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 { prng } from "@syntest/prng"; - -import { StatementPool } from "../../../StatementPool"; -import { ObjectFunctionCall } from "../../../statements/action/ObjectFunctionCall"; -import { Statement } from "../../../statements/Statement"; - -import { CallGenerator } from "./CallGenerator"; - -export class ObjectFunctionCallGenerator extends CallGenerator { - override generate( - depth: number, - variableIdentifier: string, - typeIdentifier: string, - exportIdentifier: string, - name: string, - _statementPool: StatementPool - ): ObjectFunctionCall { - const type_ = this.rootContext - .getTypeModel() - .getObjectDescription(typeIdentifier); - - const arguments_: Statement[] = this.sampleArguments(depth, type_); - - const object_ = this.sampler.sampleConstantObject( - depth + 1, - exportIdentifier - ); - - return new ObjectFunctionCall( - variableIdentifier, - typeIdentifier, - name, - prng.uniqueId(), - arguments_, - object_ - ); - } -} diff --git a/libraries/search-javascript/lib/testcase/sampling/generators/action/SetterGenerator.ts b/libraries/search-javascript/lib/testcase/sampling/generators/action/SetterGenerator.ts deleted file mode 100644 index 009ce27e5..000000000 --- a/libraries/search-javascript/lib/testcase/sampling/generators/action/SetterGenerator.ts +++ /dev/null @@ -1,59 +0,0 @@ -/* - * 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 { prng } from "@syntest/prng"; - -import { StatementPool } from "../../../StatementPool"; -import { Setter } from "../../../statements/action/Setter"; -import { Statement } from "../../../statements/Statement"; - -import { CallGenerator } from "./CallGenerator"; - -export class SetterGenerator extends CallGenerator { - override generate( - depth: number, - variableIdentifier: string, - typeIdentifier: string, - exportIdentifier: string, - name: string, - _statementPool: StatementPool - ): Setter { - const type_ = this.rootContext - .getTypeModel() - .getObjectDescription(typeIdentifier); - - const arguments_: Statement[] = this.sampleArguments(depth, type_); - - if (arguments_.length !== 1) { - throw new Error("Setter must have exactly one argument"); - } - - const constructor_ = this.sampler.sampleConstructorCall( - depth + 1, - exportIdentifier - ); - - return new Setter( - variableIdentifier, - typeIdentifier, - name, - prng.uniqueId(), - arguments_[0], - constructor_ - ); - } -} diff --git a/libraries/search-javascript/lib/testcase/sampling/generators/action/GetterGenerator.ts b/libraries/search-javascript/lib/testcase/statements/Reference.ts similarity index 50% rename from libraries/search-javascript/lib/testcase/sampling/generators/action/GetterGenerator.ts rename to libraries/search-javascript/lib/testcase/statements/Reference.ts index b86c529c7..1c0e0dbd2 100644 --- a/libraries/search-javascript/lib/testcase/sampling/generators/action/GetterGenerator.ts +++ b/libraries/search-javascript/lib/testcase/statements/Reference.ts @@ -15,33 +15,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { prng } from "@syntest/prng"; -import { StatementPool } from "../../../StatementPool"; -import { Getter } from "../../../statements/action/Getter"; -import { CallGenerator } from "./CallGenerator"; -export class GetterGenerator extends CallGenerator { - override generate( - depth: number, - variableIdentifier: string, - typeIdentifier: string, - exportIdentifier: string, - name: string, - _statementPool: StatementPool - ): Getter { - const constructor_ = this.sampler.sampleConstructorCall( - depth + 1, - exportIdentifier - ); - - return new Getter( - variableIdentifier, - typeIdentifier, - name, - prng.uniqueId(), - constructor_ - ); - } -} +export class Reference { + +} \ No newline at end of file diff --git a/libraries/search-javascript/lib/testcase/statements/Statement.ts b/libraries/search-javascript/lib/testcase/statements/Statement.ts index f01740c10..e20f58b52 100644 --- a/libraries/search-javascript/lib/testcase/statements/Statement.ts +++ b/libraries/search-javascript/lib/testcase/statements/Statement.ts @@ -22,13 +22,12 @@ import { Encoding, EncodingSampler } from "@syntest/search"; import { ContextBuilder } from "../../testbuilding/ContextBuilder"; /** - * @author Dimitri Stallenberg + * The abstract statement class */ export abstract class Statement { private _variableIdentifier: string; private _typeIdentifier: string; private _name: string; - private _ownType: TypeEnum; protected _uniqueId: string; public get variableIdentifier(): string { @@ -43,32 +42,32 @@ export abstract class Statement { return this._name; } - get ownType(): TypeEnum { - return this._ownType; - } - public get uniqueId(): string { return this._uniqueId; } /** * Constructor - * @param identifierDescription - * @param ownType - * @param uniqueId + * @param variableIdentifier the identifier that points to the variable this statement represents + * @param typeIdentifier the identifier used to choose the type of this statement + * @param name + * @param uniqueId the unique id of the statement */ protected constructor( variableIdentifier: string, typeIdentifier: string, name: string, - ownType: TypeEnum, uniqueId: string ) { this._variableIdentifier = variableIdentifier; this._typeIdentifier = typeIdentifier; this._name = name; - this._ownType = ownType; this._uniqueId = uniqueId; + + if (!name) { + console.log(name) + throw new Error("Name must be given!") + } } /** @@ -112,9 +111,20 @@ export abstract class Statement { * Note: when implementing this function please always decode the children of the statement before making getOrCreateVariableName on the context object. */ abstract decode(context: ContextBuilder): Decoding[]; + + /** + * returns the return type of statement + */ + abstract get returnType(): string; + + /** + * returns the type of statement + */ + abstract get type(): TypeEnum; } export interface Decoding { + variableName: string; decoded: string; reference: Statement; } diff --git a/libraries/search-javascript/lib/testcase/statements/action/ActionStatement.ts b/libraries/search-javascript/lib/testcase/statements/action/ActionStatement.ts index 8656acf13..c1d6a05eb 100644 --- a/libraries/search-javascript/lib/testcase/statements/action/ActionStatement.ts +++ b/libraries/search-javascript/lib/testcase/statements/action/ActionStatement.ts @@ -16,7 +16,7 @@ * limitations under the License. */ -import { Export, TypeEnum } from "@syntest/analysis-javascript"; +import { Action } from "@syntest/analysis-javascript"; import { Encoding, EncodingSampler, shouldNeverHappen } from "@syntest/search"; import { Statement } from "../Statement"; @@ -25,21 +25,29 @@ import { Statement } from "../Statement"; * ActionStatement */ export abstract class ActionStatement extends Statement { - private _args: Statement[]; - protected _export?: Export; + protected _action: Action; + private _children: Statement[]; + private _parent: Statement | undefined protected constructor( variableIdentifier: string, typeIdentifier: string, name: string, - ownType: TypeEnum, uniqueId: string, - arguments_: Statement[], - export_?: Export + action: Action, + children: Statement[], + parent?: Statement | undefined ) { - super(variableIdentifier, typeIdentifier, name, ownType, uniqueId); - this._args = arguments_; - this._export = export_; + super(variableIdentifier, typeIdentifier, name, uniqueId); + this._action = action; + this._children = children; + this._parent = parent + + for (const child of children) { + if (!child) { + throw new Error("Invalid arg") + } + } } abstract override mutate( @@ -49,31 +57,51 @@ export abstract class ActionStatement extends Statement { abstract override copy(): ActionStatement; - setChild(index: number, newChild: Statement) { + hasChildren(): boolean { + return this._children.length > 0 || !this._parent; + } + + getChildren(): Statement[] { + const children = [...this._children] + if (this._parent) { + children.push(this._parent) + } + return children; + } + + override setChild(index: number, newChild: Statement) { if (!newChild) { throw new Error("Invalid new child!"); } - if (index < 0 || index >= this.args.length) { + if (index < 0 || index > this.children.length) { + throw new Error(shouldNeverHappen(`Invalid index used index: ${index}`)); + } + + if (index === this.children.length && !this.parent) { throw new Error(shouldNeverHappen(`Invalid index used index: ${index}`)); } - this.args[index] = newChild; + if (index === this.children.length) { + this._parent = newChild; + } else { + this._children[index] = newChild; + } } - hasChildren(): boolean { - return this._args.length > 0; + get action() { + return this._action; } - getChildren(): Statement[] { - return [...this._args]; + protected get parent(): Statement { + return this._parent } - protected get args(): Statement[] { - return this._args; + protected get children(): Statement[] { + return this._children; } - get export() { - return this._export; + override get returnType(): string { + return this.typeIdentifier } } diff --git a/libraries/search-javascript/lib/testcase/statements/action/ClassActionStatement.ts b/libraries/search-javascript/lib/testcase/statements/action/ClassActionStatement.ts deleted file mode 100644 index aa6f9e791..000000000 --- a/libraries/search-javascript/lib/testcase/statements/action/ClassActionStatement.ts +++ /dev/null @@ -1,87 +0,0 @@ -/* - * 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 { TypeEnum } from "@syntest/analysis-javascript"; -import { shouldNeverHappen } from "@syntest/search"; - -import { Statement } from "../Statement"; - -import { ActionStatement } from "./ActionStatement"; -import { ConstructorCall } from "./ConstructorCall"; - -export abstract class ClassActionStatement extends ActionStatement { - private _constructor: ConstructorCall; - - /** - * Constructor - * @param identifierDescription the return type options of the function - * @param ownType the return type of the function - * @param uniqueId id of the gene - * @param methodName the name of the function - * @param args the arguments of the function - */ - constructor( - variableIdentifier: string, - typeIdentifier: string, - name: string, - ownType: TypeEnum, - uniqueId: string, - arguments_: Statement[], - constructor_: ConstructorCall - ) { - super( - variableIdentifier, - typeIdentifier, - name, - ownType, - uniqueId, - arguments_ - ); - this._constructor = constructor_; - } - - override setChild(index: number, newChild: Statement) { - if (!newChild) { - throw new Error("Invalid new child!"); - } - - if (index < 0 || index > this.args.length) { - throw new Error(shouldNeverHappen(`Invalid index used index: ${index}`)); - } - - if (index === this.args.length) { - if (!(newChild instanceof ConstructorCall)) { - throw new TypeError(shouldNeverHappen("should be a constructor")); - } - this._constructor = newChild; - } else { - this.args[index] = newChild; - } - } - - override hasChildren(): boolean { - return true; - } - - override getChildren(): Statement[] { - return [...this.args, this._constructor]; - } - - get constructor_() { - return this._constructor; - } -} diff --git a/libraries/search-javascript/lib/testcase/statements/action/ConstructorCall.ts b/libraries/search-javascript/lib/testcase/statements/action/ConstructorCall.ts index f4e8db67b..ffa685610 100644 --- a/libraries/search-javascript/lib/testcase/statements/action/ConstructorCall.ts +++ b/libraries/search-javascript/lib/testcase/statements/action/ConstructorCall.ts @@ -16,7 +16,7 @@ * limitations under the License. */ -import { Export, TypeEnum } from "@syntest/analysis-javascript"; +import { Action, TypeEnum } from "@syntest/analysis-javascript"; import { prng } from "@syntest/prng"; import { ContextBuilder } from "../../../testbuilding/ContextBuilder"; @@ -29,103 +29,97 @@ import { ActionStatement } from "./ActionStatement"; * ConstructorCall */ export class ConstructorCall extends ActionStatement { - private _classIdentifier: string; - get classIdentifier(): string { - return this._classIdentifier; - } - - /** - * Constructor - * @param type the return identifierDescription of the constructor - * @param uniqueId optional argument - * @param args the arguments of the constructor - */ constructor( variableIdentifier: string, typeIdentifier: string, - classIdentifier: string, name: string, uniqueId: string, + action: Action, arguments_: Statement[], - export_: Export + parent: Statement ) { super( variableIdentifier, typeIdentifier, name, - TypeEnum.FUNCTION, uniqueId, + action, arguments_, - export_ + parent ); - this._classIdentifier = classIdentifier; } mutate(sampler: JavaScriptTestCaseSampler, depth: number): ConstructorCall { if (prng.nextBoolean(sampler.deltaMutationProbability)) { - const arguments_ = this.args.map((a: Statement) => a.copy()); + let parent = this.parent.copy() + const arguments_ = this.children.map((a: Statement) => a.copy()); - if (arguments_.length > 0) { - const index = prng.nextInt(0, arguments_.length - 1); + const index = prng.nextInt(0, arguments_.length); + + if (index < arguments_.length) { arguments_[index] = arguments_[index].mutate(sampler, depth + 1); + } else { + parent = parent.mutate(sampler, depth + 1) } return new ConstructorCall( this.variableIdentifier, this.typeIdentifier, - this._classIdentifier, this.name, prng.uniqueId(), + this.action, arguments_, - this.export + parent ); } else { - return sampler.constructorCallGenerator.generate( + return sampler.sampleSpecificConstructorCall( depth, - this.variableIdentifier, - this.typeIdentifier, - this.export.id, - this.name, - sampler.statementPool + this.action ); } } copy(): ConstructorCall { - const deepCopyArguments = this.args.map((a: Statement) => a.copy()); + const deepCopyArguments = this.children.map((a: Statement) => a.copy()); return new ConstructorCall( this.variableIdentifier, this.typeIdentifier, - this._classIdentifier, this.name, this.uniqueId, + this.action, deepCopyArguments, - this.export + this.parent.copy() ); } decode(context: ContextBuilder): Decoding[] { - const argumentsDecoding: Decoding[] = this.args.flatMap((a) => + const parentDecoding = this.parent.decode(context) + const argumentsDecoding: Decoding[] = this.children.flatMap((a) => a.decode(context) ); - const arguments_ = this.args + const arguments_ = this.children .map((a) => context.getOrCreateVariableName(a)) .join(", "); - const import_ = context.getOrCreateImportName(this.export); - const decoded = `const ${context.getOrCreateVariableName( - this - )} = new ${import_}(${arguments_})`; + const decoded = `new ${context.getOrCreateVariableName(this.parent)}(${arguments_})`; return [ + ...parentDecoding, ...argumentsDecoding, { + variableName: context.getOrCreateVariableName( + this + ), decoded: decoded, reference: this, }, ]; } + + override get type(): TypeEnum { + return TypeEnum.OBJECT + } } diff --git a/libraries/search-javascript/lib/testcase/statements/action/FunctionCall.ts b/libraries/search-javascript/lib/testcase/statements/action/FunctionCall.ts index 0907d551f..d114623fc 100644 --- a/libraries/search-javascript/lib/testcase/statements/action/FunctionCall.ts +++ b/libraries/search-javascript/lib/testcase/statements/action/FunctionCall.ts @@ -16,7 +16,7 @@ * limitations under the License. */ -import { Export, TypeEnum } from "@syntest/analysis-javascript"; +import { Action, TypeEnum } from "@syntest/analysis-javascript"; import { prng } from "@syntest/prng"; import { ContextBuilder } from "../../../testbuilding/ContextBuilder"; @@ -26,86 +26,96 @@ import { Decoding, Statement } from "../Statement"; import { ActionStatement } from "./ActionStatement"; /** - * @author Dimitri Stallenberg + * FunctionCall */ export class FunctionCall extends ActionStatement { - /** - * Constructor - * @param uniqueId id of the gene - * @param functionName the name of the function - * @param args the arguments of the function - */ + constructor( variableIdentifier: string, typeIdentifier: string, name: string, uniqueId: string, + action: Action, arguments_: Statement[], - export_: Export + parent: Statement ) { super( variableIdentifier, typeIdentifier, name, - TypeEnum.FUNCTION, uniqueId, + action, arguments_, - export_ + parent ); } mutate(sampler: JavaScriptTestCaseSampler, depth: number): FunctionCall { // replace entire function call - const arguments_ = this.args.map((a: Statement) => a.copy()); - - if (arguments_.length > 0) { - const index = prng.nextInt(0, arguments_.length - 1); - arguments_[index] = arguments_[index].mutate(sampler, depth + 1); - } + const arguments_ = this.children.map((a: Statement) => a.copy()); - return new FunctionCall( - this.variableIdentifier, - this.typeIdentifier, - this.name, - prng.uniqueId(), - arguments_, - this.export - ); + let parent = this.parent.copy() + const index = prng.nextInt(0, arguments_.length) + + if (index < arguments_.length) { + arguments_[index] = arguments_[index].mutate(sampler, depth + 1); + } else { + parent = parent.mutate(sampler, depth + 1) + } + + return new FunctionCall( + this.variableIdentifier, + this.typeIdentifier, + this.name, + prng.uniqueId(), + this.action, + arguments_, + parent + ); + } copy(): FunctionCall { - const deepCopyArguments = this.args.map((a: Statement) => a.copy()); + const deepCopyArguments = this.children.map((a: Statement) => a.copy()); return new FunctionCall( this.variableIdentifier, this.typeIdentifier, this.name, this.uniqueId, + this.action, deepCopyArguments, - this.export + this.parent.copy() ); } decode(context: ContextBuilder): Decoding[] { - const argumentDecoding: Decoding[] = this.args.flatMap((a) => - a.decode(context) - ); + const objectDecoding = this.parent.decode(context) - const arguments_ = this.args - .map((a) => context.getOrCreateVariableName(a)) - .join(", "); + const argumentDecoding: Decoding[] = this.children.flatMap((a) => + a.decode(context) + ); - const import_ = context.getOrCreateImportName(this.export); - const decoded = `const ${context.getOrCreateVariableName( - this - )} = await ${import_}(${arguments_})`; + const arguments_ = this.children + .map((a) => context.getOrCreateVariableName(a)) + .join(", "); + + const decoded = `await ${context.getOrCreateVariableName(this.parent)}(${arguments_})`; + + return [ + ...objectDecoding, + ...argumentDecoding, + { + variableName: context.getOrCreateVariableName( + this + ), + decoded: decoded, + reference: this, + }, + ]; + } - return [ - ...argumentDecoding, - { - decoded: decoded, - reference: this, - }, - ]; + override get type(): TypeEnum { + return TypeEnum.FUNCTION } } diff --git a/libraries/search-javascript/lib/testcase/statements/action/ConstantObject.ts b/libraries/search-javascript/lib/testcase/statements/action/ImportStatement.ts similarity index 57% rename from libraries/search-javascript/lib/testcase/statements/action/ConstantObject.ts rename to libraries/search-javascript/lib/testcase/statements/action/ImportStatement.ts index 5106b19da..8e28ed3c4 100644 --- a/libraries/search-javascript/lib/testcase/statements/action/ConstantObject.ts +++ b/libraries/search-javascript/lib/testcase/statements/action/ImportStatement.ts @@ -16,72 +16,69 @@ * limitations under the License. */ -import { Export, TypeEnum } from "@syntest/analysis-javascript"; -import { prng } from "@syntest/prng"; +import { Action, TypeEnum } from "@syntest/analysis-javascript"; import { ContextBuilder } from "../../../testbuilding/ContextBuilder"; import { JavaScriptTestCaseSampler } from "../../sampling/JavaScriptTestCaseSampler"; -import { Decoding } from "../Statement"; +import { Decoding, Statement } from "../Statement"; import { ActionStatement } from "./ActionStatement"; + /** - * @author Dimitri Stallenberg + * ImportStatement */ -export class ConstantObject extends ActionStatement { +export class ImportStatement extends ActionStatement { + constructor( variableIdentifier: string, typeIdentifier: string, name: string, uniqueId: string, - export_: Export + action: Action, ) { super( variableIdentifier, typeIdentifier, name, - TypeEnum.OBJECT, uniqueId, - [], - export_ + action, + [] ); } - mutate(sampler: JavaScriptTestCaseSampler, depth: number): ConstantObject { - // delta mutations are non existance here so we make a copy instead - return prng.nextBoolean(sampler.deltaMutationProbability) - ? this.copy() - : sampler.constantObjectGenerator.generate( - depth, - this.variableIdentifier, - this.typeIdentifier, - this.export.id, - this.name, - sampler.statementPool - ); + mutate(_sampler: JavaScriptTestCaseSampler, _depth: number): ImportStatement { + // nothing to mutate actually + return this.copy() } - copy(): ConstantObject { - return new ConstantObject( + copy(): ImportStatement { + return new ImportStatement( this.variableIdentifier, this.typeIdentifier, this.name, this.uniqueId, - this.export + this.action ); } decode(context: ContextBuilder): Decoding[] { - const import_ = context.getOrCreateImportName(this.export); - const decoded = `const ${context.getOrCreateVariableName( - this - )} = ${import_}`; - return [ { - decoded: decoded, + variableName: context.getOrCreateVariableName( + this + ), + decoded: `SHOULD NOT BE USED`, reference: this, }, ]; } + + override setChild(_index: number, _newChild: Statement): void { + throw new Error("Import statement does not have children") + } + + override get type(): TypeEnum { + return TypeEnum.NULL + } } diff --git a/libraries/search-javascript/lib/testcase/statements/action/Getter.ts b/libraries/search-javascript/lib/testcase/statements/action/MemberStatement.ts similarity index 52% rename from libraries/search-javascript/lib/testcase/statements/action/Getter.ts rename to libraries/search-javascript/lib/testcase/statements/action/MemberStatement.ts index f49387978..9fb213b7b 100644 --- a/libraries/search-javascript/lib/testcase/statements/action/Getter.ts +++ b/libraries/search-javascript/lib/testcase/statements/action/MemberStatement.ts @@ -16,82 +16,85 @@ * limitations under the License. */ -import { TypeEnum } from "@syntest/analysis-javascript"; +import { Action, TypeEnum } from "@syntest/analysis-javascript"; import { prng } from "@syntest/prng"; import { ContextBuilder } from "../../../testbuilding/ContextBuilder"; import { JavaScriptTestCaseSampler } from "../../sampling/JavaScriptTestCaseSampler"; -import { Decoding } from "../Statement"; +import { Decoding, Statement } from "../Statement"; + +import { ActionStatement } from "./ActionStatement"; -import { ClassActionStatement } from "./ClassActionStatement"; -import { ConstructorCall } from "./ConstructorCall"; /** - * @author Dimitri Stallenberg + * MemberStatement */ -export class Getter extends ClassActionStatement { - /** - * Constructor - * @param identifierDescription the return type options of the function - * @param type the type of property - * @param uniqueId id of the gene - * @param property the name of the property - */ +export class MemberStatement extends ActionStatement { + private key: string + constructor( variableIdentifier: string, typeIdentifier: string, name: string, uniqueId: string, - constructor_: ConstructorCall + action: Action, + parent: Statement, + key: string ) { super( variableIdentifier, typeIdentifier, name, - TypeEnum.FUNCTION, uniqueId, + action, [], - constructor_ + parent ); + this.key = key } - mutate(sampler: JavaScriptTestCaseSampler, depth: number): Getter { - const constructor_ = this.constructor_.mutate(sampler, depth + 1); - - return new Getter( - this.variableIdentifier, - this.typeIdentifier, - this.name, - prng.uniqueId(), - constructor_ - ); + mutate(sampler: JavaScriptTestCaseSampler, depth: number): MemberStatement { + return prng.nextBoolean(sampler.deltaMutationProbability) ? new MemberStatement( + this.variableIdentifier, + this.typeIdentifier, + this.name, + prng.uniqueId(), + this.action, + this.parent.mutate(sampler, depth + 1), + this.key + ) : sampler.sampleMemberStatement( + depth, + this.action, + this.key + ); } - copy(): Getter { - return new Getter( + copy(): MemberStatement { + return new MemberStatement( this.variableIdentifier, this.typeIdentifier, this.name, this.uniqueId, - this.constructor_.copy() + this.action, + this.parent.copy(), + this.key ); } decode(context: ContextBuilder): Decoding[] { - const constructorDecoding = this.constructor_.decode(context); - - const decoded = `const ${context.getOrCreateVariableName( - this - )} = await ${context.getOrCreateVariableName(this.constructor_)}.${ - this.name - }`; - return [ - ...constructorDecoding, + ...this.parent.decode(context), { - decoded: decoded, + variableName: context.getOrCreateVariableName( + this + ), + decoded: `${context.getOrCreateVariableName(this.parent)}.${this.name}`, reference: this, }, ]; } + + override get type(): TypeEnum { + return TypeEnum.NULL + } } diff --git a/libraries/search-javascript/lib/testcase/statements/action/MethodCall.ts b/libraries/search-javascript/lib/testcase/statements/action/MethodCall.ts deleted file mode 100644 index 90d0904a6..000000000 --- a/libraries/search-javascript/lib/testcase/statements/action/MethodCall.ts +++ /dev/null @@ -1,119 +0,0 @@ -/* - * 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 { TypeEnum } from "@syntest/analysis-javascript"; -import { prng } from "@syntest/prng"; - -import { ContextBuilder } from "../../../testbuilding/ContextBuilder"; -import { JavaScriptTestCaseSampler } from "../../sampling/JavaScriptTestCaseSampler"; -import { Decoding, Statement } from "../Statement"; - -import { ClassActionStatement } from "./ClassActionStatement"; -import { ConstructorCall } from "./ConstructorCall"; - -/** - * MethodCall - */ -export class MethodCall extends ClassActionStatement { - /** - * Constructor - * @param identifierDescription the return type options of the function - * @param uniqueId id of the gene - * @param methodName the name of the function - * @param args the arguments of the function - */ - constructor( - variableIdentifier: string, - typeIdentifier: string, - name: string, - uniqueId: string, - arguments_: Statement[], - constructor_: ConstructorCall - ) { - super( - variableIdentifier, - typeIdentifier, - name, - TypeEnum.FUNCTION, - uniqueId, - arguments_, - constructor_ - ); - } - - mutate(sampler: JavaScriptTestCaseSampler, depth: number): MethodCall { - const arguments_ = this.args.map((a: Statement) => a.copy()); - let constructor_ = this.constructor_.copy(); - const index = prng.nextInt(0, arguments_.length); - - if (index < arguments_.length) { - // go over each arg - arguments_[index] = arguments_[index].mutate(sampler, depth + 1); - } else { - constructor_ = constructor_.mutate(sampler, depth + 1); - } - - return new MethodCall( - this.variableIdentifier, - this.typeIdentifier, - this.name, - prng.uniqueId(), - arguments_, - constructor_ - ); - } - - copy(): MethodCall { - const deepCopyArguments = this.args.map((a: Statement) => a.copy()); - - return new MethodCall( - this.variableIdentifier, - this.typeIdentifier, - this.name, - this.uniqueId, - deepCopyArguments, - this.constructor_.copy() - ); - } - - decode(context: ContextBuilder): Decoding[] { - const constructorDecoding = this.constructor_.decode(context); - const argumentsDecoding: Decoding[] = this.args.flatMap((a) => - a.decode(context) - ); - - const arguments_ = this.args - .map((a) => context.getOrCreateVariableName(a)) - .join(", "); - - const decoded = `const ${context.getOrCreateVariableName( - this - )} = await ${context.getOrCreateVariableName(this.constructor_)}.${ - this.name - }(${arguments_})`; - - return [ - ...constructorDecoding, - ...argumentsDecoding, - { - decoded: decoded, - reference: this, - }, - ]; - } -} diff --git a/libraries/search-javascript/lib/testcase/statements/action/ObjectFunctionCall.ts b/libraries/search-javascript/lib/testcase/statements/action/ObjectFunctionCall.ts deleted file mode 100644 index c356b9d2e..000000000 --- a/libraries/search-javascript/lib/testcase/statements/action/ObjectFunctionCall.ts +++ /dev/null @@ -1,154 +0,0 @@ -/* - * 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 { TypeEnum } from "@syntest/analysis-javascript"; -import { prng } from "@syntest/prng"; -import { shouldNeverHappen } from "@syntest/search"; - -import { ContextBuilder } from "../../../testbuilding/ContextBuilder"; -import { JavaScriptTestCaseSampler } from "../../sampling/JavaScriptTestCaseSampler"; -import { Decoding, Statement } from "../Statement"; - -import { ActionStatement } from "./ActionStatement"; -import { ConstantObject } from "./ConstantObject"; - -/** - * @author Dimitri Stallenberg - */ -export class ObjectFunctionCall extends ActionStatement { - private _object: ConstantObject; - - /** - * Constructor - * @param identifierDescription the return type options of the function - * @param type the return type of the function - * @param uniqueId id of the gene - * @param methodName the name of the function - * @param args the arguments of the function - */ - constructor( - variableIdentifier: string, - typeIdentifier: string, - name: string, - uniqueId: string, - arguments_: Statement[], - object_: ConstantObject - ) { - super( - variableIdentifier, - typeIdentifier, - name, - TypeEnum.FUNCTION, - uniqueId, - arguments_ - ); - this._object = object_; - } - - mutate( - sampler: JavaScriptTestCaseSampler, - depth: number - ): ObjectFunctionCall { - const arguments_ = this.args.map((a: Statement) => a.copy()); - let object_ = this._object.copy(); - const index = prng.nextInt(0, arguments_.length); - - if (index < arguments_.length) { - // go over each arg - arguments_[index] = arguments_[index].mutate(sampler, depth + 1); - } else { - object_ = object_.mutate(sampler, depth + 1); - } - - return new ObjectFunctionCall( - this.variableIdentifier, - this.typeIdentifier, - this.name, - prng.uniqueId(), - arguments_, - object_ - ); - } - - override setChild(index: number, newChild: Statement) { - if (!newChild) { - throw new Error("Invalid new child!"); - } - - if (index < 0 || index > this.args.length) { - throw new Error(shouldNeverHappen(`Invalid index used index: ${index}`)); - } - - if (index === this.args.length) { - if (!(newChild instanceof ConstantObject)) { - throw new TypeError(shouldNeverHappen("should be a constant object")); - } - this._object = newChild; - } else { - this.args[index] = newChild; - } - } - - override hasChildren(): boolean { - return true; - } - - override getChildren(): Statement[] { - return [...this.args, this._object]; - } - - copy(): ObjectFunctionCall { - const deepCopyArguments = this.args.map((a: Statement) => a.copy()); - - return new ObjectFunctionCall( - this.variableIdentifier, - this.typeIdentifier, - this.name, - this.uniqueId, - deepCopyArguments, - this._object.copy() - ); - } - - decode(context: ContextBuilder): Decoding[] { - const objectDecoding = this._object.decode(context); - - const argumentsDecoding: Decoding[] = this.args.flatMap((a) => - a.decode(context) - ); - - const arguments_ = this.args - .map((a) => context.getOrCreateVariableName(a)) - .join(", "); - - const decoded = `const ${context.getOrCreateVariableName( - this - )} = await ${context.getOrCreateVariableName(this._object)}.${ - this.name - }(${arguments_})`; - - return [ - ...objectDecoding, - ...argumentsDecoding, - { - decoded: decoded, - reference: this, - }, - ]; - } -} diff --git a/libraries/search-javascript/lib/testcase/statements/action/Setter.ts b/libraries/search-javascript/lib/testcase/statements/action/Setter.ts deleted file mode 100644 index 60e44ee65..000000000 --- a/libraries/search-javascript/lib/testcase/statements/action/Setter.ts +++ /dev/null @@ -1,121 +0,0 @@ -/* - * 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 { TypeEnum } from "@syntest/analysis-javascript"; -import { prng } from "@syntest/prng"; - -import { ContextBuilder } from "../../../testbuilding/ContextBuilder"; -import { JavaScriptTestCaseSampler } from "../../sampling/JavaScriptTestCaseSampler"; -import { Decoding, Statement } from "../Statement"; - -import { ClassActionStatement } from "./ClassActionStatement"; -import { ConstructorCall } from "./ConstructorCall"; -import { Getter } from "./Getter"; -import { MethodCall } from "./MethodCall"; - -/** - * @author Dimitri Stallenberg - */ -export class Setter extends ClassActionStatement { - /** - * Constructor - * @param identifierDescription the return type options of the function - * @param type always void - * @param uniqueId id of the gene - * @param property the name of the property - * @param arg the argument of the setter - */ - constructor( - variableIdentifier: string, - typeIdentifier: string, - name: string, - uniqueId: string, - argument: Statement, - constructor_: ConstructorCall - ) { - super( - variableIdentifier, - typeIdentifier, - name, - TypeEnum.FUNCTION, - uniqueId, - [argument], - constructor_ - ); - } - - mutate( - sampler: JavaScriptTestCaseSampler, - depth: number - ): Getter | Setter | MethodCall { - let argument = this.args.map((a: Statement) => a.copy())[0]; - let constructor_ = this.constructor_.copy(); - - if (prng.nextBoolean(0.5)) { - argument = argument.mutate(sampler, depth + 1); - } else { - constructor_ = constructor_.mutate(sampler, depth + 1); - } - - return new Setter( - this.variableIdentifier, - this.typeIdentifier, - this.name, - prng.uniqueId(), - argument, - constructor_ - ); - } - - copy(): Setter { - const deepCopyArgument = this.args.map((a: Statement) => a.copy())[0]; - - return new Setter( - this.variableIdentifier, - this.typeIdentifier, - this.name, - this.uniqueId, - deepCopyArgument, - this.constructor_.copy() - ); - } - - decode(context: ContextBuilder): Decoding[] { - const constructorDecoding = this.constructor_.decode(context); - const argumentDecoding: Decoding[] = this.args.flatMap((a) => - a.decode(context) - ); - - const argument = this.args - .map((a) => context.getOrCreateVariableName(a)) - .join(", "); - - const decoded = `${context.getOrCreateVariableName(this.constructor_)}.${ - this.name - } = ${argument}`; - - return [ - ...constructorDecoding, - ...argumentDecoding, - { - decoded: decoded, - reference: this, - }, - ]; - } -} diff --git a/libraries/search-javascript/lib/testcase/statements/complex/ArrayStatement.ts b/libraries/search-javascript/lib/testcase/statements/complex/ArrayStatement.ts index ddc216554..2989ec42a 100644 --- a/libraries/search-javascript/lib/testcase/statements/complex/ArrayStatement.ts +++ b/libraries/search-javascript/lib/testcase/statements/complex/ArrayStatement.ts @@ -25,7 +25,7 @@ import { JavaScriptTestCaseSampler } from "../../sampling/JavaScriptTestCaseSamp import { Decoding, Statement } from "../Statement"; /** - * @author Dimitri Stallenberg + * ArrayStatement */ export class ArrayStatement extends Statement { private _elements: Statement[]; @@ -37,7 +37,7 @@ export class ArrayStatement extends Statement { uniqueId: string, elements: Statement[] ) { - super(variableIdentifier, typeIdentifier, name, TypeEnum.ARRAY, uniqueId); + super(variableIdentifier, typeIdentifier, name, uniqueId); this._elements = elements; // check for circular @@ -137,13 +137,14 @@ export class ArrayStatement extends Statement { .map((a) => context.getOrCreateVariableName(a)) .join(", "); - const decoded = `const ${context.getOrCreateVariableName( - this - )} = [${elements}]`; + const decoded = `[${elements}]`; return [ ...elementStatements, { + variableName: context.getOrCreateVariableName( + this + ), decoded: decoded, reference: this, }, @@ -173,4 +174,12 @@ export class ArrayStatement extends Statement { protected get children(): Statement[] { return this._elements; } + + override get returnType(): string { + return TypeEnum.ARRAY + } + + override get type(): TypeEnum { + return this.returnType +} } diff --git a/libraries/search-javascript/lib/testcase/statements/complex/ArrowFunctionStatement.ts b/libraries/search-javascript/lib/testcase/statements/complex/ArrowFunctionStatement.ts index 84a1d3717..6e7aaed55 100644 --- a/libraries/search-javascript/lib/testcase/statements/complex/ArrowFunctionStatement.ts +++ b/libraries/search-javascript/lib/testcase/statements/complex/ArrowFunctionStatement.ts @@ -25,7 +25,7 @@ import { JavaScriptTestCaseSampler } from "../../sampling/JavaScriptTestCaseSamp import { Decoding, Statement } from "../Statement"; /** - * @author Dimitri Stallenberg + * Arrow function statement */ export class ArrowFunctionStatement extends Statement { private _parameters: string[]; @@ -43,7 +43,6 @@ export class ArrowFunctionStatement extends Statement { variableIdentifier, typeIdentifier, name, - TypeEnum.FUNCTION, uniqueId ); this._parameters = parameters; @@ -59,7 +58,7 @@ export class ArrowFunctionStatement extends Statement { this.name, prng.uniqueId(), this._parameters, - this.returnValue + this._returnValue ? this._returnValue.mutate(sampler, depth + 1) : undefined ); @@ -99,9 +98,10 @@ export class ArrowFunctionStatement extends Statement { if (this._returnValue === undefined) { return [ { - decoded: `const ${context.getOrCreateVariableName( + variableName: context.getOrCreateVariableName( this - )} = (${this._parameters.join(", ")}) => {};`, + ), + decoded: `(${this._parameters.join(", ")}) => {};`, reference: this, }, ]; @@ -109,15 +109,16 @@ export class ArrowFunctionStatement extends Statement { const returnStatement: Decoding[] = this._returnValue.decode(context); - const decoded = `const ${context.getOrCreateVariableName( - this - )} = (${this._parameters.join( + const decoded = `(${this._parameters.join( ", " - )}) => { return ${context.getOrCreateVariableName(this.returnValue)} };`; + )}) => { return ${context.getOrCreateVariableName(this._returnValue)} };`; return [ ...returnStatement, { + variableName: context.getOrCreateVariableName( + this + ), decoded: decoded, reference: this, }, @@ -128,7 +129,7 @@ export class ArrowFunctionStatement extends Statement { if (this._returnValue === undefined) { return []; } - return [this.returnValue]; + return [this._returnValue]; } hasChildren(): boolean { @@ -147,7 +148,11 @@ export class ArrowFunctionStatement extends Statement { this._returnValue = newChild; } - get returnValue(): Statement { - return this._returnValue; + override get returnType(): string { + return TypeEnum.FUNCTION } + + override get type(): TypeEnum { + return this.returnType +} } diff --git a/libraries/search-javascript/lib/testcase/statements/complex/ObjectStatement.ts b/libraries/search-javascript/lib/testcase/statements/complex/ObjectStatement.ts index fafdb6733..dd2153e4d 100644 --- a/libraries/search-javascript/lib/testcase/statements/complex/ObjectStatement.ts +++ b/libraries/search-javascript/lib/testcase/statements/complex/ObjectStatement.ts @@ -24,12 +24,13 @@ import { ContextBuilder } from "../../../testbuilding/ContextBuilder"; import { JavaScriptTestCaseSampler } from "../../sampling/JavaScriptTestCaseSampler"; import { Decoding, Statement } from "../Statement"; -/** - * @author Dimitri Stallenberg - */ type ObjectType = { [key: string]: Statement | undefined; }; + +/** + * Object Statement + */ export class ObjectStatement extends Statement { private _object: ObjectType; @@ -40,7 +41,7 @@ export class ObjectStatement extends Statement { uniqueId: string, object: ObjectType ) { - super(variableIdentifier, typeIdentifier, name, TypeEnum.OBJECT, uniqueId); + super(variableIdentifier, typeIdentifier, name, uniqueId); this._object = object; // check for circular @@ -179,13 +180,14 @@ export class ObjectStatement extends Statement { ) .join(","); - const decoded = `const ${context.getOrCreateVariableName( - this - )} = {${children}${children.length > 0 ? "\n\t\t" : ""}}`; + const decoded = `{${children}${children.length > 0 ? "\n\t\t" : ""}}`; return [ ...childStatements, { + variableName: context.getOrCreateVariableName( + this + ), decoded: decoded, reference: this, }, @@ -219,4 +221,12 @@ export class ObjectStatement extends Statement { this._object[key] = newChild; } + + override get returnType(): string { + return TypeEnum.OBJECT + } + + override get type(): TypeEnum { + return this.returnType +} } diff --git a/libraries/search-javascript/lib/testcase/statements/primitive/BoolStatement.ts b/libraries/search-javascript/lib/testcase/statements/literal/BoolStatement.ts similarity index 91% rename from libraries/search-javascript/lib/testcase/statements/primitive/BoolStatement.ts rename to libraries/search-javascript/lib/testcase/statements/literal/BoolStatement.ts index 1208b81fd..7b48f84e4 100644 --- a/libraries/search-javascript/lib/testcase/statements/primitive/BoolStatement.ts +++ b/libraries/search-javascript/lib/testcase/statements/literal/BoolStatement.ts @@ -22,12 +22,12 @@ import { prng } from "@syntest/prng"; import { JavaScriptTestCaseSampler } from "../../sampling/JavaScriptTestCaseSampler"; import { Statement } from "../Statement"; -import { PrimitiveStatement } from "./PrimitiveStatement"; +import { LiteralStatement } from "./LiteralStatement"; /** * @author Dimitri Stallenberg */ -export class BoolStatement extends PrimitiveStatement { +export class BoolStatement extends LiteralStatement { constructor( variableIdentifier: string, typeIdentifier: string, @@ -39,7 +39,6 @@ export class BoolStatement extends PrimitiveStatement { variableIdentifier, typeIdentifier, name, - TypeEnum.BOOLEAN, uniqueId, value ); @@ -74,4 +73,8 @@ export class BoolStatement extends PrimitiveStatement { this.value ); } + + get returnType() { + return TypeEnum.BOOLEAN + } } diff --git a/libraries/search-javascript/lib/testcase/statements/primitive/IntegerStatement.ts b/libraries/search-javascript/lib/testcase/statements/literal/IntegerStatement.ts similarity index 94% rename from libraries/search-javascript/lib/testcase/statements/primitive/IntegerStatement.ts rename to libraries/search-javascript/lib/testcase/statements/literal/IntegerStatement.ts index da6047b2e..998bde691 100644 --- a/libraries/search-javascript/lib/testcase/statements/primitive/IntegerStatement.ts +++ b/libraries/search-javascript/lib/testcase/statements/literal/IntegerStatement.ts @@ -22,15 +22,13 @@ import { prng } from "@syntest/prng"; import { JavaScriptTestCaseSampler } from "../../sampling/JavaScriptTestCaseSampler"; import { Statement } from "../Statement"; +import { LiteralStatement } from "./LiteralStatement"; import { NumericStatement } from "./NumericStatement"; -import { PrimitiveStatement } from "./PrimitiveStatement"; /** * Generic number class - * - * @author Dimitri Stallenberg */ -export class IntegerStatement extends PrimitiveStatement { +export class IntegerStatement extends LiteralStatement { constructor( variableIdentifier: string, typeIdentifier: string, @@ -42,7 +40,6 @@ export class IntegerStatement extends PrimitiveStatement { variableIdentifier, typeIdentifier, name, - TypeEnum.INTEGER, uniqueId, Math.round(value) ); @@ -118,4 +115,8 @@ export class IntegerStatement extends PrimitiveStatement { this.value ); } + + get returnType() { + return TypeEnum.INTEGER + } } diff --git a/libraries/search-javascript/lib/testcase/statements/primitive/PrimitiveStatement.ts b/libraries/search-javascript/lib/testcase/statements/literal/LiteralStatement.ts similarity index 84% rename from libraries/search-javascript/lib/testcase/statements/primitive/PrimitiveStatement.ts rename to libraries/search-javascript/lib/testcase/statements/literal/LiteralStatement.ts index 5e996ffbb..07944fbdc 100644 --- a/libraries/search-javascript/lib/testcase/statements/primitive/PrimitiveStatement.ts +++ b/libraries/search-javascript/lib/testcase/statements/literal/LiteralStatement.ts @@ -22,10 +22,7 @@ import { ContextBuilder } from "../../../testbuilding/ContextBuilder"; import { JavaScriptTestCaseSampler } from "../../sampling/JavaScriptTestCaseSampler"; import { Decoding, Statement } from "../Statement"; -/** - * @author Dimitri Stallenberg - */ -export abstract class PrimitiveStatement extends Statement { +export abstract class LiteralStatement extends Statement { get value(): T { return this._value; } @@ -35,11 +32,10 @@ export abstract class PrimitiveStatement extends Statement { variableIdentifier: string, typeIdentifier: string, name: string, - type: TypeEnum, uniqueId: string, value: T ) { - super(variableIdentifier, typeIdentifier, name, type, uniqueId); + super(variableIdentifier, typeIdentifier, name, uniqueId); this._value = value; } @@ -70,11 +66,18 @@ export abstract class PrimitiveStatement extends Statement { const asString = String(this.value); return [ { - decoded: `const ${context.getOrCreateVariableName( + variableName: context.getOrCreateVariableName( this - )} = ${asString};`, + ), + decoded: `${asString};`, reference: this, }, ]; } + + abstract override get returnType(): TypeEnum; + + override get type(): TypeEnum { + return this.returnType + } } diff --git a/libraries/search-javascript/lib/testcase/statements/primitive/NullStatement.ts b/libraries/search-javascript/lib/testcase/statements/literal/NullStatement.ts similarity index 90% rename from libraries/search-javascript/lib/testcase/statements/primitive/NullStatement.ts rename to libraries/search-javascript/lib/testcase/statements/literal/NullStatement.ts index bb3937b4d..1b0860a56 100644 --- a/libraries/search-javascript/lib/testcase/statements/primitive/NullStatement.ts +++ b/libraries/search-javascript/lib/testcase/statements/literal/NullStatement.ts @@ -22,12 +22,12 @@ import { prng } from "@syntest/prng"; import { JavaScriptTestCaseSampler } from "../../sampling/JavaScriptTestCaseSampler"; import { Statement } from "../Statement"; -import { PrimitiveStatement } from "./PrimitiveStatement"; +import { LiteralStatement } from "./LiteralStatement"; /** - * @author Dimitri Stallenberg + * Null Statement */ -export class NullStatement extends PrimitiveStatement { +export class NullStatement extends LiteralStatement { constructor( variableIdentifier: string, typeIdentifier: string, @@ -38,7 +38,6 @@ export class NullStatement extends PrimitiveStatement { variableIdentifier, typeIdentifier, name, - TypeEnum.NULL, uniqueId, // eslint-disable-next-line unicorn/no-null null @@ -72,4 +71,8 @@ export class NullStatement extends PrimitiveStatement { this.uniqueId ); } + + get returnType() { + return TypeEnum.NULL + } } diff --git a/libraries/search-javascript/lib/testcase/statements/primitive/NumericStatement.ts b/libraries/search-javascript/lib/testcase/statements/literal/NumericStatement.ts similarity index 94% rename from libraries/search-javascript/lib/testcase/statements/primitive/NumericStatement.ts rename to libraries/search-javascript/lib/testcase/statements/literal/NumericStatement.ts index fcbaf00f0..1ddb77068 100644 --- a/libraries/search-javascript/lib/testcase/statements/primitive/NumericStatement.ts +++ b/libraries/search-javascript/lib/testcase/statements/literal/NumericStatement.ts @@ -23,14 +23,12 @@ import { JavaScriptTestCaseSampler } from "../../sampling/JavaScriptTestCaseSamp import { Statement } from "../Statement"; import { IntegerStatement } from "./IntegerStatement"; -import { PrimitiveStatement } from "./PrimitiveStatement"; +import { LiteralStatement } from "./LiteralStatement"; /** * Generic number class - * - * @author Dimitri Stallenberg */ -export class NumericStatement extends PrimitiveStatement { +export class NumericStatement extends LiteralStatement { constructor( variableIdentifier: string, typeIdentifier: string, @@ -42,7 +40,6 @@ export class NumericStatement extends PrimitiveStatement { variableIdentifier, typeIdentifier, name, - TypeEnum.NUMERIC, uniqueId, value ); @@ -118,4 +115,8 @@ export class NumericStatement extends PrimitiveStatement { this.value ); } + + get returnType() { + return TypeEnum.NUMERIC + } } diff --git a/libraries/search-javascript/lib/testcase/statements/primitive/StringStatement.ts b/libraries/search-javascript/lib/testcase/statements/literal/StringStatement.ts similarity index 95% rename from libraries/search-javascript/lib/testcase/statements/primitive/StringStatement.ts rename to libraries/search-javascript/lib/testcase/statements/literal/StringStatement.ts index 19d301225..b34195df5 100644 --- a/libraries/search-javascript/lib/testcase/statements/primitive/StringStatement.ts +++ b/libraries/search-javascript/lib/testcase/statements/literal/StringStatement.ts @@ -23,12 +23,12 @@ import { ContextBuilder } from "../../../testbuilding/ContextBuilder"; import { JavaScriptTestCaseSampler } from "../../sampling/JavaScriptTestCaseSampler"; import { Decoding, Statement } from "../Statement"; -import { PrimitiveStatement } from "./PrimitiveStatement"; +import { LiteralStatement } from "./LiteralStatement"; /** - * @author Dimitri Stallenberg + * StringStatement */ -export class StringStatement extends PrimitiveStatement { +export class StringStatement extends LiteralStatement { constructor( variableIdentifier: string, typeIdentifier: string, @@ -40,7 +40,6 @@ export class StringStatement extends PrimitiveStatement { variableIdentifier, typeIdentifier, name, - TypeEnum.STRING, uniqueId, value ); @@ -207,9 +206,14 @@ export class StringStatement extends PrimitiveStatement { return [ { - decoded: `const ${context.getOrCreateVariableName(this)} = "${value}";`, + variableName: context.getOrCreateVariableName(this), + decoded: `"${value}";`, reference: this, }, ]; } + + get returnType() { + return TypeEnum.STRING + } } diff --git a/libraries/search-javascript/lib/testcase/statements/primitive/UndefinedStatement.ts b/libraries/search-javascript/lib/testcase/statements/literal/UndefinedStatement.ts similarity index 90% rename from libraries/search-javascript/lib/testcase/statements/primitive/UndefinedStatement.ts rename to libraries/search-javascript/lib/testcase/statements/literal/UndefinedStatement.ts index 6b59d139f..a28557e1c 100644 --- a/libraries/search-javascript/lib/testcase/statements/primitive/UndefinedStatement.ts +++ b/libraries/search-javascript/lib/testcase/statements/literal/UndefinedStatement.ts @@ -22,12 +22,12 @@ import { prng } from "@syntest/prng"; import { JavaScriptTestCaseSampler } from "../../sampling/JavaScriptTestCaseSampler"; import { Statement } from "../Statement"; -import { PrimitiveStatement } from "./PrimitiveStatement"; +import { LiteralStatement } from "./LiteralStatement"; /** - * @author Dimitri Stallenberg + * UndefinedStatement */ -export class UndefinedStatement extends PrimitiveStatement { +export class UndefinedStatement extends LiteralStatement { constructor( variableIdentifier: string, typeIdentifier: string, @@ -38,7 +38,6 @@ export class UndefinedStatement extends PrimitiveStatement { variableIdentifier, typeIdentifier, name, - TypeEnum.UNDEFINED, uniqueId, // eslint-disable-next-line unicorn/no-useless-undefined undefined @@ -72,4 +71,8 @@ export class UndefinedStatement extends PrimitiveStatement { this.uniqueId ); } + + get returnType() { + return TypeEnum.UNDEFINED + } } diff --git a/tools/javascript/lib/JavaScriptLauncher.ts b/tools/javascript/lib/JavaScriptLauncher.ts index 6d7b724dd..0bf3163d3 100644 --- a/tools/javascript/lib/JavaScriptLauncher.ts +++ b/tools/javascript/lib/JavaScriptLauncher.ts @@ -20,18 +20,17 @@ import * as path from "node:path"; import { AbstractSyntaxTreeFactory, + ActionFactory, ConstantPoolFactory, ControlFlowGraphFactory, DependencyFactory, ExportFactory, InferenceTypeModelFactory, - isExported, RootContext, Target, TargetFactory, TypeExtractor, TypeModelFactory, - ActionFactory, } from "@syntest/analysis-javascript"; import { ArgumentsObject, @@ -425,7 +424,7 @@ export class JavaScriptLauncher extends Launcher { ); JavaScriptLauncher.LOGGER.info("Gathering actions"); - await this.rootContext.getAllActions(); + await this.rootContext.extractAllActions(); const startTypeResolving = Date.now(); JavaScriptLauncher.LOGGER.info("Extracting types"); @@ -742,16 +741,16 @@ export class JavaScriptLauncher extends Launcher { JavaScriptLauncher.LOGGER.info( `Testing target ${target.name} in ${target.path}` ); + const actions = this.rootContext.getAllActions().get(target.path) const currentSubject = new JavaScriptSubject( target, + actions, this.rootContext, (this.arguments_).syntaxForgiving, this.arguments_.stringAlphabet ); - const rootTargets = currentSubject - .getActionableTargets() - .filter((target) => isExported(target)); + const rootTargets = currentSubject.actions if (rootTargets.length === 0) { JavaScriptLauncher.LOGGER.info( @@ -833,6 +832,7 @@ export class JavaScriptLauncher extends Launcher { sampler: sampler, }); + console.log('generating algo') const algorithm = (>( this.moduleManager.getPlugin( PluginType.SearchAlgorithm, @@ -877,6 +877,7 @@ export class JavaScriptLauncher extends Launcher { ); } + console.log('starting search') // This searches for a covering population const archive = await algorithm.search( currentSubject,