This commit is contained in:
2025-07-09 13:26:51 +01:00
commit d564cf2cd1
24 changed files with 19728 additions and 0 deletions

3
.gitignore vendored Normal file
View File

@@ -0,0 +1,3 @@
output.csv
output/
node_modules/

18
.vscode/launch.json vendored Normal file
View File

@@ -0,0 +1,18 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "launch",
"name": "Launch Program",
"skipFiles": [
"<node_internals>/**"
],
"program": "${workspaceFolder}/src/index.mjs",
"args": ["${workspaceFolder}/test_src/index.js"],
}
]
}

View File

@@ -0,0 +1,46 @@
J$.iids = {"9":[1,10,1,17],"17":[1,18,1,27],"25":[1,10,1,28],"33":[1,10,1,28],"41":[1,10,1,28],"49":[2,15,2,22],"57":[2,23,2,32],"65":[2,15,2,33],"73":[2,15,2,33],"81":[2,15,2,33],"89":[4,1,4,8],"97":[4,13,4,16],"105":[4,1,4,17],"107":[4,1,4,12],"113":[4,1,4,17],"121":[7,15,7,16],"129":[7,12,7,17],"137":[7,12,7,17],"145":[7,5,7,18],"153":[6,1,8,2],"161":[6,1,8,2],"169":[10,16,10,18],"177":[10,30,10,46],"185":[10,16,10,47],"187":[10,16,10,29],"193":[10,16,10,47],"201":[10,16,10,47],"209":[11,1,11,8],"217":[11,30,11,38],"225":[11,1,11,39],"227":[11,1,11,12],"233":[11,1,11,40],"241":[1,1,11,40],"249":[1,1,11,40],"257":[1,1,11,40],"265":[6,1,8,2],"273":[1,1,11,40],"281":[1,1,11,40],"289":[6,1,8,2],"297":[6,1,8,2],"305":[1,1,11,40],"313":[1,1,11,40],"nBranches":0,"originalCodeFileName":"/home/atreyab/Documents/Docs/SlicingImport/repos-js/safeImport/test_src/jalangiinstrument.cjs","instrumentedCodeFileName":"/home/atreyab/Documents/Docs/SlicingImport/repos-js/safeImport/test_src/jalangiinstrument._jalangi_.js","code":"var fs = require('node:fs');\nvar process = require('process');\n\nconsole.log('a')\n\nfunction x(){\n return {x:3};\n}\n\nvar newLocal = fs.existsSync(\"./package.json\");\nconsole.log(`Read some data`,newLocal);"};
jalangiLabel1:
while (true) {
try {
J$.Se(241, '/home/atreyab/Documents/Docs/SlicingImport/repos-js/safeImport/test_src/jalangiinstrument._jalangi_.js', '/home/atreyab/Documents/Docs/SlicingImport/repos-js/safeImport/test_src/jalangiinstrument.cjs');
function x() {
jalangiLabel0:
while (true) {
try {
J$.Fe(153, arguments.callee, this, arguments);
arguments = J$.N(161, 'arguments', arguments, 4);
return J$.X1(145, J$.Rt(137, J$.T(129, {
x: J$.T(121, 3, 22, false)
}, 11, false)));
} catch (J$e) {
J$.Ex(289, J$e);
} finally {
if (J$.Fr(297))
continue jalangiLabel0;
else
return J$.Ra();
}
}
}
J$.N(249, 'fs', fs, 0);
J$.N(257, 'process', process, 0);
x = J$.N(273, 'x', J$.T(265, x, 12, false, 153), 0);
J$.N(281, 'newLocal', newLocal, 0);
var fs = J$.X1(41, J$.W(33, 'fs', J$.F(25, J$.R(9, 'require', require, 2), 0)(J$.T(17, 'node:fs', 21, false)), fs, 3));
var process = J$.X1(81, J$.W(73, 'process', J$.F(65, J$.R(49, 'require', require, 2), 0)(J$.T(57, 'process', 21, false)), process, 3));
J$.X1(113, J$.M(105, J$.R(89, 'console', console, 2), 'log', 0)(J$.T(97, 'a', 21, false)));
var newLocal = J$.X1(201, J$.W(193, 'newLocal', J$.M(185, J$.R(169, 'fs', fs, 1), 'existsSync', 0)(J$.T(177, "./package.json", 21, false)), newLocal, 3));
J$.X1(233, J$.M(225, J$.R(209, 'console', console, 2), 'log', 0)(`Read some data`, J$.R(217, 'newLocal', newLocal, 1)));
} catch (J$e) {
J$.Ex(305, J$e);
} finally {
if (J$.Sr(313)) {
J$.L();
continue jalangiLabel1;
} else {
J$.L();
break jalangiLabel1;
}
}
}
// JALANGI DO NOT INSTRUMENT

View File

@@ -0,0 +1 @@
{"9":[1,10,1,17],"17":[1,18,1,27],"25":[1,10,1,28],"33":[1,10,1,28],"41":[1,10,1,28],"49":[2,15,2,22],"57":[2,23,2,32],"65":[2,15,2,33],"73":[2,15,2,33],"81":[2,15,2,33],"89":[4,1,4,8],"97":[4,13,4,16],"105":[4,1,4,17],"107":[4,1,4,12],"113":[4,1,4,17],"121":[7,15,7,16],"129":[7,12,7,17],"137":[7,12,7,17],"145":[7,5,7,18],"153":[6,1,8,2],"161":[6,1,8,2],"169":[10,16,10,18],"177":[10,30,10,46],"185":[10,16,10,47],"187":[10,16,10,29],"193":[10,16,10,47],"201":[10,16,10,47],"209":[11,1,11,8],"217":[11,30,11,38],"225":[11,1,11,39],"227":[11,1,11,12],"233":[11,1,11,40],"241":[1,1,11,40],"249":[1,1,11,40],"257":[1,1,11,40],"265":[6,1,8,2],"273":[1,1,11,40],"281":[1,1,11,40],"289":[6,1,8,2],"297":[6,1,8,2],"305":[1,1,11,40],"313":[1,1,11,40],"nBranches":0,"originalCodeFileName":"/home/atreyab/Documents/Docs/SlicingImport/repos-js/safeImport/test_src/jalangiinstrument.cjs","instrumentedCodeFileName":"/home/atreyab/Documents/Docs/SlicingImport/repos-js/safeImport/test_src/jalangiinstrument._jalangi_.js","code":"var fs = require('node:fs');\nvar process = require('process');\n\nconsole.log('a')\n\nfunction x(){\n return {x:3};\n}\n\nvar newLocal = fs.existsSync(\"./package.json\");\nconsole.log(`Read some data`,newLocal);"}

View File

@@ -0,0 +1,12 @@
// not supported
var {existsSync} = require('node:fs');
var process = require('process');
console.log('a')
function x(){
return {x:3};
}
var newLocal = existsSync("./package.json");
console.log(`Read some data`,newLocal);

View File

@@ -0,0 +1,5 @@
const { readFileSync } = require('fs');
module.exports.x = function x() {
return readFileSync("path.json").toString();
}

View File

@@ -0,0 +1,11 @@
var fs = require('node:fs');
var process = require('process');
console.log('a')
function x(){
return {x:3};
}
var newLocal = fs.existsSync("./package.json");
console.log(`Read some data`,newLocal);

1398
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

30
package.json Normal file
View File

@@ -0,0 +1,30 @@
{
"name": "safeimport",
"version": "1.0.0",
"main": "src/index.mjs",
"type": "module",
"scripts": {
"start": "node src/index.mjs",
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC",
"description": "",
"dependencies": {
"csv": "^6.3.11",
"download-counts": "^2.20250701.0",
"eslint-scope": "^8.4.0",
"esprima": "^4.0.1",
"esquery": "^1.6.0",
"esrecurse": "^4.3.0",
"estraverse": "^5.3.0",
"jalangi2": "^0.2.6",
"semver": "^7.7.2",
"webpack": "^5.99.9"
},
"devDependencies": {
"@types/eslint-scope": "^8.3.0",
"@types/estree": "^1.0.8",
"@types/node": "^24.0.0"
}
}

42
src/ast/analysis.mjs Normal file
View File

@@ -0,0 +1,42 @@
import { parseScript} from 'esprima';
import fs from 'node:fs';
import * as eslintScope from 'eslint-scope';
import { prependString, appendString } from '../utils/constants.mjs';
// const modulesImported
/**
*
* @param {string} filePath
* @returns
*/
export function getASTAndScope(filePath) {
const mod = fs.readFileSync(filePath);
const modString = mod.toString();
const nodeJSCJSModString = prependString + modString + appendString;
const parseOptions = { ecmaVersion: 7, range: true, sourceType: 'script', comment: true };
const parsedModAST = parseScript(nodeJSCJSModString, parseOptions);
// https://eslint.org/docs/latest/extend/scope-manager-interface#fields-1
// handle cjs ourselves
const scopeManager = eslintScope.analyze(parsedModAST, { ...parseOptions, nodejsScope: false });
return { scopeManager, parsedModAST };
}
/**
*
* @param {import('eslint').Scope.ScopeManager} scopeManager
* @returns
*/
export function getSetOfIdentifierReferencesForRequireUses(scopeManager) {
const requireImportsUsed = scopeManager.scopes[1].set.get('require');
return new Set(requireImportsUsed.references.map(e => e.identifier));
}

11
src/ast/tag.mjs Normal file
View File

@@ -0,0 +1,11 @@
export function tagASTNode(importVariableReference, _variableName, moduleImportedName) {
importVariableReference.identifier.tag = "ref:variable:" + _variableName + "_module_" + moduleImportedName;
}
export function untagASTNode(importVariableReference, _variableName, moduleImportedName) {
delete importVariableReference.identifier.tag
}
export function getTagKey(){
return 'tag';
}

104
src/ast/visitors.mjs Normal file
View File

@@ -0,0 +1,104 @@
import { Visitor } from "esrecurse"
export class ExpressionArrayVisitor extends Visitor {
/**
*
* @param {import('eslint').Scope.ScopeManager} scopeManager
*/
constructor(scopeManager) {
super();
/**
* @type {import('estree').Literal["value"][]}
*/
this.arguments = [];
}
/**
*
* @param {import('estree').CallExpression} node
* @returns
*/
CallExpression(node) {
for (const argumentNode of node.arguments) {
this.visit(argumentNode);
}
}
/**
*
* @param {import('estree').Expression} node
*/
Expression(node) {
return this.visit(node.arguments);
}
/**
*
* @param {SpreadExpression} node
*/
SpreadExpression(node) {
throw Error("No Spreads!");
}
/**
*
* @param {import("estree").Identifier} node
*/
Identifier(node) {
// TODO - Grab it from outside
console.error("Found identifier ", node);
throw Error("constant or nothing");
}
/**
*
* @param {import("estree").Literal} node
*/
Literal(node) {
this.arguments.push(node.value);
}
ObjectExpression(node) {
console.warning("Not finished");
throw Error("TBD");
this.arguments.push(new ObjectSimplifierVisitor().visit(node));
}
/**
*
* @param {import("estree").Node} node
*/
visit(node) {
console.log("Visiting", node.type);
super.visit(node);
}
}
class ObjectSimplifierVisitor extends Visitor {
expr = {};
exprStack = [];
// /**
// *
// * @param {import("estree").ObjectExpression} node
// */
// ObjectExpression(node){
// }
/**
*
* @param {import("estree").Property} node
*/
Property(node){
this.#topOfStack()?.[ node.key.value];
}
visit(node) {
console.log("Objvisit",node);
return super.visit(node);
}
#topOfStack(){
return this.exprStack[this.exprStack.length-1];
}
}

96
src/calls.mjs Normal file
View File

@@ -0,0 +1,96 @@
import { Syntax } from 'esprima';
import esquery from 'esquery';
import { getSetOfIdentifierReferencesForRequireUses } from './ast/analysis.mjs';
import { LibraryCallsRecorder } from './libcalls.mjs';
import { tagASTNode, getTagKey, untagASTNode } from './ast/tag.mjs';
import { ExpressionArrayVisitor } from './ast/visitors.mjs';
import assert from 'assert';
/**
*
* @param {import('eslint').Scope.ScopeManager} scopeManager
* @returns
*/
export function getRequireCallsAndConstantArgs(scopeManager) {
const requireReferencesIdentifierSet = getSetOfIdentifierReferencesForRequireUses(scopeManager);
const callRecorder = new LibraryCallsRecorder();
// all the variables, what part of require module they're using
for (const scope of scopeManager.scopes) {
for (const [variableName, variable] of scope.set) {
// FIXME raise error if more than one but has import
const declareDefinesOfVariable = variable.defs.filter(e => e.node.type === Syntax.VariableDeclarator && e.node.init.type === Syntax.CallExpression);
// uses this variable
const declarationNodesUsingRequireList = declareDefinesOfVariable.filter(e => requireReferencesIdentifierSet.has(e.node.init.callee));
if (declarationNodesUsingRequireList.length > 1) {
console.error(`Import variable ${variableName} has been defined twice, skipping`);
continue;
} else if (declarationNodesUsingRequireList.length === 0) {
console.log(`Skipping unused import variable ${variableName}`);
continue;
}
const declarationNodeUsingRequire = declarationNodesUsingRequireList[0]; // we know its singleton from above
//
const moduleImportedName = getModuleNameFromRequireAssignDeclaration(declarationNodeUsingRequire);
const importPortion = getModuleImportPortionFromDefinition(declarationNodeUsingRequire, variableName);
for (const importVariableReference of variable.references) {
tagASTNode(importVariableReference, variableName, moduleImportedName);
const simpleCallExpressionsOfVariableInBlockList = esquery.query(importVariableReference.from.block, `CallExpression:has(>[tag="${importVariableReference.identifier[getTagKey()]}"])`);
for (const simpleCallExpressionOfVariableInBlock of simpleCallExpressionsOfVariableInBlockList) {
const argumentsGathered = gatherArgumentsFromTheCallExpression(importVariableReference, simpleCallExpressionOfVariableInBlock, importVariableReference.from.block, scopeManager);
callRecorder.pushToMap(moduleImportedName, importPortion, argumentsGathered);
}
untagASTNode(importVariableReference, variableName, moduleImportedName)
}
}
}
return callRecorder.calls;
}/**
*
* @param {import('eslint').Scope.Definition} declaratorDefinition
*/
export function getModuleImportPortionFromDefinition(declaratorDefinition, variableName) {
const node = declaratorDefinition.node;
// console.log(`Req type`, node.type);
// FIXME - import portion calculations correctly
if (node.id.type === 'ObjectPattern') {
// console.log("Obj");
// const nodeName = node.id.properties.filter(e=>e.key.value.name==='x')
// TODO allow re-naming
return "." + variableName;
} else {
return '.';
}
}
/**
*
* @param {import('eslint').Scope.Reference} importVariableReference
* @param {import('estree').CallExpression} callExpressionNode
* @param {ReturnType<esquery>[0]} contextOfUse
* @param {import('eslint').Scope.ScopeManager} scopeManager
*/
export function gatherArgumentsFromTheCallExpression(importVariableReference, callExpressionNode, contextOfUse, scopeManager) {
const expressionArrayVisitor = new ExpressionArrayVisitor(scopeManager);
expressionArrayVisitor.visit(callExpressionNode);
const { arguments: constantArguments } = expressionArrayVisitor;
return constantArguments;
}
/**
*
* @param {Scope.Definition} requireUsingReference
* @returns {string}
*/
export function getModuleNameFromRequireAssignDeclaration(requireUsingReference) {
assert(requireUsingReference.node.init.arguments.length === 1);
const moduleImported = requireUsingReference.node.init?.arguments?.[0]?.value ?? null;
assert(moduleImported !== null, "Module has to exist");
return moduleImported;
}

80
src/index.mjs Normal file
View File

@@ -0,0 +1,80 @@
import assert from 'node:assert';
import { getASTAndScope } from './ast/analysis.mjs';
import { getRequireCallsAndConstantArgs } from './calls.mjs';
import { analyze, instrumentString, instrumentDir } from 'jalangi2';
import { readFileSync ,realpathSync} from 'node:fs';
import {getSliceAndInfoSync} from 'slice-js/dist/slice-code/test/helpers/utils.js';
import { dirname,join } from 'node:path';
/**
* Call parameter generation
*/
function main() {
const FILE_PATH = './test_src/index.cjs';
const { scopeManager, _parsedModAST } = getASTAndScope(FILE_PATH);
assert(scopeManager.scopes.length >= 2, "expected atleast global and module scope");
assert(scopeManager.scopes[1].type === 'function', "expected the 'module' scope to have function scope");
const calls = getRequireCallsAndConstantArgs(scopeManager);
for (const [moduleName, callBoxes] of calls.entries()) {
if (moduleName.startsWith('.')) {
console.log('Importing', moduleName, callBoxes);
} else {
console.log(`Module "${moduleName}" - System module. FIXME skipping`);
}
}
console.log(`Call List`, calls);
for (const [moduleName, callBox] of calls) {
// console.log(callBox);
if (!moduleName.startsWith('.')) {
continue;
}
const relatedModuleNamePath = join(realpathSync(dirname(FILE_PATH)) ,moduleName);
const fileSource = readFileSync(relatedModuleNamePath).toString('utf-8');
const {slicedCode} = getSliceAndInfoSync(fileSource, (moduleExports) => {
return [...callBox.entries()].flatMap(([methodName, methodArgsList])=>{
const methodNameNormed = methodName.substring(1);
console.log("Calls for ",methodNameNormed,methodArgsList)
return methodArgsList.map(methodArgsList=>moduleExports[methodNameNormed].apply(moduleExports[methodNameNormed],methodArgsList));
})
},relatedModuleNamePath);
console.log(`Sliced code ${moduleName}\n`,slicedCode);
}
}
function jalangiInstrumentMain() {
const FILE_PATH = './test_src/index.cjs';
const fileString = readFileSync(FILE_PATH).toString();
const y = instrumentString(fileString, {});
console.log(y);
}
/**
* Analysis POC
*/
function jalangiAnalyzeMain() {
const FILE_PATH = './test_src/index.cjs';
const y = analyze(FILE_PATH, ["./node_modules/jalangi2/src/js/sample_analyses/tutorial/LogAll.js", "./src_analysis/analysisCallbackTemplate.cjs"]);
// const x = 5;
y.then(yp => {
console.log("Analysis complete", yp);
}).catch(console.error).finally(kek => {
console.log("Threw error", kek);
})
}
if (process.argv[1] === import.meta.filename) {
console.log("[SafeImport] started")
main();
}

35
src/libcalls.mjs Normal file
View File

@@ -0,0 +1,35 @@
//@ts-check
/**
* @typedef {import('estree').Literal["value"]} GenericLiteralType
*/
/**
* Record library calls
*/
export class LibraryCallsRecorder{
/**
* @type {Map<string,Map<string,GenericLiteralType[][]>>}
*/
#calls = new Map();
/**
*
* @param {string} moduleName
* @param {string} libraryFunctionSegment
* @param {any[]} argumentsCalled
*/
pushToMap(moduleName, libraryFunctionSegment, argumentsCalled){
const modulePortion = this.#calls.get(moduleName)?? new Map();
const defArgs = modulePortion.get(libraryFunctionSegment) ?? [];
defArgs.push(argumentsCalled);
modulePortion.set(libraryFunctionSegment,defArgs);
this.#calls.set(moduleName, modulePortion);
}
get calls(){
return this.#calls;
}
}

5
src/slice-js.d.ts vendored Normal file
View File

@@ -0,0 +1,5 @@
declare module 'slice-js' {
declare async function slice(fileName: string, name: string, tester: (...args:any[])=>any);
}
export = slice;

3
src/utils/constants.mjs Normal file
View File

@@ -0,0 +1,3 @@
export const prependString = `(function(exports, require, module, __filename, __dirname) {\n`
export const appendString = `\n});`

View File

@@ -0,0 +1,647 @@
/*
* Copyright 2014 Samsung Information Systems America, Inc.
*
* 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.
*/
// Author: Koushik Sen
// do not remove the following comment
// JALANGI DO NOT INSTRUMENT
/**
* @file A template for writing a Jalangi 2 analysis
* @author Koushik Sen
*
*/
(function (sandbox) {
/**
* <p>
* This file is a template for writing a custom Jalangi 2 analysis. Simply copy this file and rewrite the
* callbacks that you need to implement in your analysis. Other callbacks should be removed from the file.
*</p>
*
* <p>
* In the following methods (also called as callbacks) one can choose to not return anything.
* If all of the callbacks return nothing, we get a passive analysis where the
* concrete execution happens unmodified and callbacks can be used to observe the execution.
* One can choose to return suitable objects with specified properties in some callbacks
* to modify the behavior of the concrete execution. For example, one could set the skip
* property of the object returned from {@link MyAnalysis#putFieldPre} to true to skip the actual putField operation.
* Similarly, one could set the result field of the object returned from a {@link MyAnalysis#write} callback
* to modify the value that is actually written to a variable. The result field of the object
* returned from a {@link MyAnalysis#conditional} callback can be suitably set to change the control-flow of the
* program execution. In {@link MyAnalysis#functionExit} and {@link MyAnalysis#scriptExit},
* one can set the <tt>isBacktrack</tt> property of the returned object to true to reexecute the body of
* the function from the beginning. This in conjunction with the ability to change the
* control-flow of a program enables us to explore the different paths of a function in
* symbolic execution.
* </p>
*
* <p>
* Note that if <tt>process.exit()</tt> is called, then an execution terminates abnormally and a callback to
* {@link MyAnalysis#endExecution} will be skipped.
* </p>
*
* <p>
* An analysis can access the source map, which maps instruction identifiers to source locations,
* using the global object stored in <code>J$.smap</code>. Jalangi 2
* assigns a unique id, called <code>sid</code>, to each JavaScript
* script loaded at runtime. <code>J$.smap</code> maps each <code>sid</code> to an object, say
* <code>iids</code>, containing source map information for the script whose id is <code>sid</code>.
* <code>iids</code> has the following properties: <code>"originalCodeFileName"</code> (stores the path of the original
* script file), <code>"instrumentedCodeFileName"</code> (stores the path of the instrumented script file),
* <code>"url"</code> (is optional and stores the URL of the script if it is set during instrumentation
* using the --url option),
* <code>"evalSid"</code> (stores the sid of the script in which the eval is called in case the current script comes from
* an <code>eval</code> function call),
* <code>"evalIid"</code> (iid of the <code>eval</code> function call in case the current script comes from an
* <code>eval</code> function call), <code>"nBranches"</code> (the number of conditional statements
* in the script),
* and <code>"code"</code> (a string denoting the original script code if the code is instrumented with the
* --inlineSource option).
* <code>iids</code> also maps each <code>iid</code> (which stands for instruction id, an unique id assigned
* to each callback function inserted by Jalangi2) to an array containing
* <code>[beginLineNumber, beginColumnNumber, endLineNumber, endColumnNumber]</code>. The mapping from iids
* to arrays is only available if the code is instrumented with
* the --inlineIID option.
* </p>
* <p>
* In each callback described below, <code>iid</code> denotes the unique static instruction id of the callback in the script.
* Two callback functions inserted in two different scripts may have the same iid. In a callback function, one can access
* the current script id using <code>J$.sid</code>. One can call <code>J$.getGlobalIID(iid)</code> to get a string, called
* <code>giid</code>, that statically identifies the
* callback throughout the program. <code>J$.getGlobalIID(iid)</code> returns the string <code>J$.sid+":"+iid</code>.
* <code>J$.iidToLocation(giid)</code> returns a string
* containing the original script file path, begin and end line numbers and column numbers of the code snippet
* for which the callback with <code>giid</code> was inserted.
*
* </p>
* <p>
* A number of sample analyses can be found at {@link ../src/js/sample_analyses/}. Refer to {@link ../README.md} for instructions
* on running an analysis.
* </p>
*
*
*
* @global
* @class
*/
function MyAnalysis() {
/**
* This callback is called before a function, method, or constructor invocation.
* Note that a method invocation also triggers a {@link MyAnalysis#getFieldPre} and a
* {@link MyAnalysis#getField} callbacks.
*
* @example
* y.f(a, b, c)
*
* // the above call roughly gets instrumented as follows:
*
* var skip = false;
* var aret = analysis.invokeFunPre(113, f, y, [a, b, c], false, true);
* if (aret) {
* f = aret.f;
* y = aret.y;
* args = aret.args;
* skip = aret.skip
* }
* if (!skip) {
* f.apply(y, args);
* }
*
* @param {number} iid - Static unique instruction identifier of this callback
* @param {function} f - The function object that going to be invoked
* @param {object} base - The receiver object for the function <tt>f</tt>
* @param {Array} args - The array of arguments passed to <tt>f</tt>
* @param {boolean} isConstructor - True if <tt>f</tt> is invoked as a constructor
* @param {boolean} isMethod - True if <tt>f</tt> is invoked as a method
* @param {number} functionIid - The iid (i.e. the unique instruction identifier) where the function was created
* @param {number} functionSid - The sid (i.e. the unique script identifier) where the function was created
* {@link MyAnalysis#functionEnter} when the function <tt>f</tt> is executed. The <tt>functionIid</tt> can be
* treated as the static identifier of the function <tt>f</tt>. Note that a given function code block can
* create several function objects, but each such object has a common <tt>functionIid</tt>, which is the iid
* that is passed to {@link MyAnalysis#functionEnter} when the function executes.
* @returns {{f: function, base: Object, args: Array, skip: boolean}|undefined} - If an object is returned and
* the <tt>skip</tt> property of the object is true, then the invocation operation is skipped.
* Original <tt>f</tt>, <tt>base</tt>, and <tt>args</tt> are replaced with that from the returned object if
* an object is returned.
*
*/
this.invokeFunPre = function (iid, f, base, args, isConstructor, isMethod, functionIid, functionSid) {
console.log("Attempting to call xyz")
return {f: f, base: base, args: args, skip: false};
};
/**
* This callback is called after a function, method, or constructor invocation.
*
* @example
* x = y.f(a, b, c)
*
* // the above call roughly gets instrumented as follows:
*
* var skip = false;
* var aret = analysis.invokeFunPre(113, f, y, [a, b, c], false, true);
* if (aret) {
* f = aret.f;
* y = aret.y;
* args = aret.args;
* skip = aret.skip
* }
* if (!skip) {
* result =f.apply(y, args);
* }
* aret = analysis.invokeFun(117, f, y, args, result, false, true);
* if (aret) {
* x = aret.result
* } else {
* x = result;
* }
*
* @param {number} iid - Static unique instruction identifier of this callback
* @param {function} f - The function object that was invoked
* @param {*} base - The receiver object for the function <tt>f</tt>
* @param {Array} args - The array of arguments passed to <tt>f</tt>
* @param {*} result - The value returned by the invocation
* @param {boolean} isConstructor - True if <tt>f</tt> is invoked as a constructor
* @param {boolean} isMethod - True if <tt>f</tt> is invoked as a method
* @param {number} functionIid - The iid (i.e. the unique instruction identifier) where the function was created
* @param {number} functionSid - The sid (i.e. the unique script identifier) where the function was created
* {@link MyAnalysis#functionEnter} when the function f is executed. <tt>functionIid</tt> can be treated as the
* static identifier of the function <tt>f</tt>. Note that a given function code block can create several function
* objects, but each such object has a common <tt>functionIid</tt>, which is the iid that is passed to
* {@link MyAnalysis#functionEnter} when the function executes.
* @returns {{result: *}| undefined} - If an object is returned, the return value of the invoked function is
* replaced with the value stored in the <tt>result</tt> property of the object. This enables one to change the
* value that is returned by the actual function invocation.
*
*/
this.invokeFun = function (iid, f, base, args, result, isConstructor, isMethod, functionIid, functionSid) {
return {result: result};
};
/**
* This callback is called after the creation of a literal. A literal can be a function literal, an object literal,
* an array literal, a number, a string, a boolean, a regular expression, null, NaN, Infinity, or undefined.
*
* @example
* x = "Hello"
*
* // the above call roughly gets instrumented as follows:
*
* var result = "Hello";
* var aret = analysis.literal(201, result, false);
* if (aret) {
* result = aret.result;
* }
* x = result;
*
*
* @param {number} iid - Static unique instruction identifier of this callback
* @param {*} val - The literal value
* @param {boolean} hasGetterSetter - True if the literal is an object and the object defines getters and setters
* @returns {{result: *} | undefined} - If the function returns an object, then the original literal value is
* replaced with the value stored in the <tt>result</tt> property of the object.
*
*/
this.literal = function (iid, val, hasGetterSetter) {
return {result: val};
};
/**
* This callback is called when a for-in loop is used to iterate the properties of an object.
*
*@example
* for (x in y) { }
*
* // the above call roughly gets instrumented as follows:
*
* var aret = analysis.forinObject(iid, y);
* if (aret) {
* y = aret.result;
* }
* for (x in y) {}
*
* @param {number} iid - Static unique instruction identifier of this callback
* @param {*} val - Objects whose properties are iterated in a for-in loop.
* @returns {{result: *} | undefined} - If the function returns an object, then the original object whose
* properties are being iterated is replaced with the value stored in the <tt>result</tt> property of the
* returned object.
*
*/
this.forinObject = function (iid, val) {
return {result: val};
};
/**
* This callback is triggered at the beginning of a scope for every local variable declared in the scope, for
* every formal parameter, for every function defined using a function statement, for <tt>arguments</tt>
* variable, and for the formal parameter passed in a catch statement.
*
* @param {number} iid - Static unique instruction identifier of this callback
* @param {string} name - Name of the variable that is declared
* @param {*} val - Initial value of the variable that is declared. Variables can be local variables, function
* parameters, catch parameters, <tt>arguments</tt>, or functions defined using function statements. Variables
* declared with <tt>var</tt> have <tt>undefined</tt> as initial values and cannot be changed by returning a
* different value from this callback. On the beginning of an execution of a function, a <tt>declare</tt>
* callback is called on the <tt>arguments</tt> variable.
* @param {boolean} isArgument - True if the variable is <tt>arguments</tt> or a formal parameter.
* @param {number} argumentIndex - Index of the argument in the function call. Indices start from 0. If the
* variable is not a formal parameter, then <tt>argumentIndex</tt> is -1.
* @param {boolean} isCatchParam - True if the variable is a parameter of a catch statement.
* @returns {{result: *} | undefined} - If the function returns an object, then the original initial value is
* replaced with the value stored in the <tt>result</tt> property of the object. This does not apply to local
* variables declared with <tt>var</tt>.
*
*/
this.declare = function (iid, name, val, isArgument, argumentIndex, isCatchParam) {
return {result: val};
};
/**
* This callback is called before a property of an object is accessed.
*
* @param {number} iid - Static unique instruction identifier of this callback
* @param {*} base - Base object
* @param {string|*} offset - Property
* @param {boolean} isComputed - True if property is accessed using square brackets. For example,
* <tt>isComputed</tt> is <tt>true</tt> if the get field operation is <tt>o[p]</tt>, and <tt>false</tt>
* if the get field operation is <tt>o.p</tt>
* @param {boolean} isOpAssign - True if the operation is of the form <code>o.p op= e</code>
* @param {boolean} isMethodCall - True if the get field operation is part of a method call (e.g. <tt>o.p()</tt>)
* @returns {{base: *, offset: *, skip: boolean} | undefined} - If an object is returned and the <tt>skip</tt>
* property of the object is true, then the get field operation is skipped. Original <tt>base</tt> and
* <tt>offset</tt> are replaced with that from the returned object if an object is returned.
*
*/
this.getFieldPre = function (iid, base, offset, isComputed, isOpAssign, isMethodCall) {
return {base: base, offset: offset, skip: false};
};
/**
* This callback is called after a property of an object is accessed.
*
* @param {number} iid - Static unique instruction identifier of this callback
* @param {*} base - Base object
* @param {string|*} offset - Property
* @param {*} val - Value of <code>base[offset]</code>
* @param {boolean} isComputed - True if property is accessed using square brackets. For example,
* <tt>isComputed</tt> is <tt>true</tt> if the get field operation is <tt>o[p]</tt>, and <tt>false</tt>
* if the get field operation is <tt>o.p</tt>
* @param {boolean} isOpAssign - True if the operation is of the form <code>o.p op= e</code>
* @param {boolean} isMethodCall - True if the get field operation is part of a method call (e.g. <tt>o.p()</tt>)
* @returns {{result: *} | undefined} - If an object is returned, the value of the get field operation is
* replaced with the value stored in the <tt>result</tt> property of the object.
*/
this.getField = function (iid, base, offset, val, isComputed, isOpAssign, isMethodCall) {
return {result: val};
};
/**
* This callback is called before a property of an object is written.
*
* @param {number} iid - Static unique instruction identifier of this callback
* @param {*} base - Base object
* @param {*} offset - Property
* @param {*} val - Value to be stored in <code>base[offset]</code>
* @param {boolean} isComputed - True if property is accessed using square brackets. For example,
* <tt>isComputed</tt> is <tt>true</tt> if the get field operation is <tt>o[p]</tt>, and <tt>false</tt>
* if the get field operation is <tt>o.p</tt>
* @param {boolean} isOpAssign - True if the operation is of the form <code>o.p op= e</code>
* @returns {{base: *, offset: *, val: *, skip: boolean} | undefined} - If an object is returned and the <tt>skip</tt>
* property is true, then the put field operation is skipped. Original <tt>base</tt>, <tt>offset</tt>, and
* <tt>val</tt> are replaced with that from the returned object if an object is returned.
*/
this.putFieldPre = function (iid, base, offset, val, isComputed, isOpAssign) {
return {base: base, offset: offset, val: val, skip: false};
};
/**
* This callback is called after a property of an object is written.
*
* @param {number} iid - Static unique instruction identifier of this callback
* @param {*} base - Base object
* @param {*} offset - Property
* @param {*} val - Value to be stored in <code>base[offset]</code>
* @param {boolean} isComputed - True if property is accessed using square brackets. For example,
* <tt>isComputed</tt> is <tt>true</tt> if the get field operation is <tt>o[p]</tt>, and <tt>false</tt>
* if the get field operation is <tt>o.p</tt>
* @param {boolean} isOpAssign - True if the operation is of the form <code>o.p op= e</code>
* @returns {{result: *} | undefined} - If an object is returned, the result of the put field operation is
* replaced with the value stored in the <tt>result</tt> property of the object.
*/
this.putField = function (iid, base, offset, val, isComputed, isOpAssign) {
return {result: val};
};
/**
* This callback is called after a variable is read.
*
* @param {number} iid - Static unique instruction identifier of this callback
* @param {string} name - Name of the variable being read
* @param {*} val - Value read from the variable
* @param {boolean} isGlobal - True if the variable is not declared using <tt>var</tt> (e.g. <tt>console</tt>)
* @param {boolean} isScriptLocal - True if the variable is declared in the global scope using <tt>var</tt>
* @returns {{result: *} | undefined} - If an object is returned, the result of the read operation is
* replaced with the value stored in the <tt>result</tt> property of the object.
*/
this.read = function (iid, name, val, isGlobal, isScriptLocal) {
return {result: val};
};
/**
* This callback is called before a variable is written.
*
* @param {number} iid - Static unique instruction identifier of this callback
* @param {string} name - Name of the variable being read
* @param {*} val - Value to be written to the variable
* @param {*} lhs - Value stored in the variable before the write operation
* @param {boolean} isGlobal - True if the variable is not declared using <tt>var</tt> (e.g. <tt>console</tt>)
* @param {boolean} isScriptLocal - True if the variable is declared in the global scope using <tt>var</tt>
* @returns {{result: *} | undefined} - If an object is returned, the result of the write operation is
* replaced with the value stored in the <tt>result</tt> property of the object.
*/
this.write = function (iid, name, val, lhs, isGlobal, isScriptLocal) {
return {result: val};
};
/**
* This callback is called before a value is returned from a function using the <tt>return</tt> keyword.
*
* @param {number} iid - Static unique instruction identifier of this callback
* @param {*} val - Value to be returned
* @returns {{result: *} | undefined} - If an object is returned, the value to be returned is
* replaced with the value stored in the <tt>result</tt> property of the object.
*/
this._return = function (iid, val) {
return {result: val};
};
/**
* This callback is called before a value is thrown using the <tt>throw</tt> keyword.
*
* @param {number} iid - Static unique instruction identifier of this callback
* @param {*} val - Value to be thrown
* @returns {{result: *} | undefined} - If an object is returned, the value to be thrown is
* replaced with the value stored in the <tt>result</tt> property of the object.
*/
this._throw = function (iid, val) {
return {result: val};
};
/**
* This callback is called when a <tt>with</tt> statement is executed
*
* @param {number} iid - Static unique instruction identifier of this callback
* @param {*} val - Value used as an argument to <tt>with</tt>
* @returns {{result: *} | undefined} - If an object is returned, the value to be used in <tt>with</tt> is
* replaced with the value stored in the <tt>result</tt> property of the object.
*/
this._with = function (iid, val) {
return {result: val};
};
/**
* This callback is called before the execution of a function body starts.
*
* @param {number} iid - Static unique instruction identifier of this callback
* @param {function} f - The function object whose body is about to get executed
* @param {*} dis - The value of the <tt>this</tt> variable in the function body
* @param {Array} args - List of the arguments with which the function is called
* @returns {undefined} - Any return value is ignored
*/
this.functionEnter = function (iid, f, dis, args) {
};
/**
* This callback is called when the execution of a function body completes
*
* @param {number} iid - Static unique instruction identifier of this callback
* @param {*} returnVal - The value returned by the function
* @param {{exception:*} | undefined} wrappedExceptionVal - If this parameter is an object, the function
* execution has thrown an uncaught exception and the exception is being stored in the <tt>exception</tt>
* property of the parameter
* @returns {{returnVal: *, wrappedExceptionVal: *, isBacktrack: boolean}} If an object is returned, then the
* actual <tt>returnVal</tt> and <tt>wrappedExceptionVal.exception</tt> are replaced with that from the
* returned object. If an object is returned and the property <tt>isBacktrack</tt> is set, then the control-flow
* returns to the beginning of the function body instead of returning to the caller. The property
* <tt>isBacktrack</tt> can be set to <tt>true</tt> to repeatedly execute the function body as in MultiSE
* symbolic execution.
*/
this.functionExit = function (iid, returnVal, wrappedExceptionVal) {
return {returnVal: returnVal, wrappedExceptionVal: wrappedExceptionVal, isBacktrack: false};
};
/**
* This callback is called before the execution of a JavaScript file
*
* @param {number} iid - Static unique instruction identifier of this callback
* @param {string} instrumentedFileName - Name of the instrumented script file
* @param {string} originalFileName - Name of the original script file
*/
this.scriptEnter = function (iid, instrumentedFileName, originalFileName) {
};
/**
* This callback is called when the execution of a JavaScript file completes
*
* @param {number} iid - Static unique instruction identifier of this callback
* @param {{exception:*} | undefined} wrappedExceptionVal - If this parameter is an object, the script
* execution has thrown an uncaught exception and the exception is being stored in the <tt>exception</tt>
* property of the parameter
* @returns {{wrappedExceptionVal: *, isBacktrack: boolean}} - If an object is returned, then the
* actual <tt>wrappedExceptionVal.exception</tt> is replaced with that from the
* returned object. If an object is returned and the property <tt>isBacktrack</tt> is set, then the control-flow
* returns to the beginning of the script body. The property
* <tt>isBacktrack</tt> can be set to <tt>true</tt> to repeatedly execute the script body as in MultiSE
* symbolic execution.
*/
this.scriptExit = function (iid, wrappedExceptionVal) {
return {wrappedExceptionVal: wrappedExceptionVal, isBacktrack: false};
};
/**
* This callback is called before a binary operation. Binary operations include +, -, *, /, %, &, |, ^,
* <<, >>, >>>, <, >, <=, >=, ==, !=, ===, !==, instanceof, delete, in. No callback for <code>delete x</code>
* because this operation cannot be performed reflectively.
*
* @param {number} iid - Static unique instruction identifier of this callback
* @param {string} op - Operation to be performed
* @param {*} left - Left operand
* @param {*} right - Right operand
* @param {boolean} isOpAssign - True if the binary operation is part of an expression of the form
* <code>x op= e</code>
* @param {boolean} isSwitchCaseComparison - True if the binary operation is part of comparing the discriminant
* with a consequent in a switch statement.
* @param {boolean} isComputed - True if the operation is of the form <code>delete x[p]</code>, and false
* otherwise (even if the operation if of the form <code>delete x.p</code>)
* @returns {{op: string, left: *, right: *, skip: boolean}|undefined} - If an object is returned and the
* <tt>skip</tt> property is true, then the binary operation is skipped. Original <tt>op</tt>, <tt>left</tt>,
* and <tt>right</tt> are replaced with that from the returned object if an object is returned.
*/
this.binaryPre = function (iid, op, left, right, isOpAssign, isSwitchCaseComparison, isComputed) {
return {op: op, left: left, right: right, skip: false};
};
/**
* This callback is called after a binary operation. Binary operations include +, -, *, /, %, &, |, ^,
* <<, >>, >>>, <, >, <=, >=, ==, !=, ===, !==, instanceof, delete, in.
*
* @param {number} iid - Static unique instruction identifier of this callback
* @param {string} op - Operation to be performed
* @param {*} left - Left operand
* @param {*} right - Right operand
* @param {*} result - The result of the binary operation
* @param {boolean} isOpAssign - True if the binary operation is part of an expression of the form
* <code>x op= e</code>
* @param {boolean} isSwitchCaseComparison - True if the binary operation is part of comparing the discriminant
* with a consequent in a switch statement.
* @param {boolean} isComputed - True if the operation is of the form <code>delete x[p]</code>, and false
* otherwise (even if the operation if of the form <code>delete x.p</code>)
* @returns {{result: *}|undefined} - If an object is returned, the result of the binary operation is
* replaced with the value stored in the <tt>result</tt> property of the object.
*/
this.binary = function (iid, op, left, right, result, isOpAssign, isSwitchCaseComparison, isComputed) {
return {result: result};
};
/**
* This callback is called before a unary operation. Unary operations include +, -, ~, !, typeof, void.
*
* @param {number} iid - Static unique instruction identifier of this callback
* @param {string} op - Operation to be performed
* @param {*} left - Left operand
* @returns {{op: *, left: *, skip: boolean} | undefined} If an object is returned and the
* <tt>skip</tt> property is true, then the unary operation is skipped. Original <tt>op</tt> and <tt>left</tt>
* are replaced with that from the returned object if an object is returned.
*/
this.unaryPre = function (iid, op, left) {
return {op: op, left: left, skip: false};
};
/**
* This callback is called after a unary operation. Unary operations include +, -, ~, !, typeof, void.
*
* @param {number} iid - Static unique instruction identifier of this callback
* @param {string} op - Operation to be performed
* @param {*} left - Left operand
* @param {*} result - The result of the unary operation
* @returns {{result: *}|undefined} - If an object is returned, the result of the unary operation is
* replaced with the value stored in the <tt>result</tt> property of the object.
*
*/
this.unary = function (iid, op, left, result) {
return {result: result};
};
/**
* This callback is called after a condition check before branching. Branching can happen in various statements
* including if-then-else, switch-case, while, for, ||, &&, ?:.
*
* @param {number} iid - Static unique instruction identifier of this callback
* @param {*} result - The value of the conditional expression
* @returns {{result: *}|undefined} - If an object is returned, the result of the conditional expression is
* replaced with the value stored in the <tt>result</tt> property of the object.
*/
this.conditional = function (iid, result) {
return {result: result};
};
/**
* This callback is called before a string passed as an argument to eval or Function is instrumented.
*
* @param {number} iid - Static unique instruction identifier of this callback
* @param {*} code - Code that is going to get instrumented
* @param {boolean} isDirect - true if this is a direct call to eval
* @returns {{code: *, skip: boolean}} - If an object is returned and the
* <tt>skip</tt> property is true, then the instrumentation of <tt>code</tt> is skipped.
* Original <tt>code</tt> is replaced with that from the returned object if an object is returned.
*/
this.instrumentCodePre = function (iid, code, isDirect) {
return {code: code, skip: false};
};
/**
* This callback is called after a string passed as an argument to eval or Function is instrumented.
*
* @param {number} iid - Static unique instruction identifier of this callback
* @param {*} newCode - Instrumented code
* @param {Object} newAst - The AST of the instrumented code
* @param {boolean} isDirect - true if this is a direct call to eval
* @returns {{result: *}|undefined} - If an object is returned, the instrumented code is
* replaced with the value stored in the <tt>result</tt> property of the object.
*/
this.instrumentCode = function (iid, newCode, newAst, isDirect) {
return {result: newCode};
};
/**
* This callback is called when an expression is evaluated and its value is discarded. For example, this
* callback is called when an expression statement completes its execution.
*
* @param {number} iid - Static unique instruction identifier of this callback
* @returns {undefined} - Any return value is ignored
*/
this.endExpression = function (iid) {
};
/**
* This callback is called when an execution terminates in node.js. In a browser environment, the callback is
* called if ChainedAnalyses.js or ChainedAnalysesNoCheck.js is used and Alt-Shift-T is pressed.
*
* @returns {undefined} - Any return value is ignored
*/
this.endExecution = function () {
};
/**
* This callback is called only when instrumented with J$.Config.ENABLE_SAMPLING = true
* This callback is called before the body of a function, method, or constructor is executed
* if returns true, instrumented function body is executed, else uninstrumented function body is executed
* @param {number} iid - Static unique instruction identifier of this callback
* @param {function} f - The function whose body is being executed
* @param {number} functionIid - The iid (i.e. the unique instruction identifier) where the function was created
* @param {number} functionSid - The sid (i.e. the unique script identifier) where the function was created
* {@link MyAnalysis#functionEnter} when the function <tt>f</tt> is executed. The <tt>functionIid</tt> can be
* treated as the static identifier of the function <tt>f</tt>. Note that a given function code block can
* create several function objects, but each such object has a common <tt>functionIid</tt>, which is the iid
* that is passed to {@link MyAnalysis#functionEnter} when the function executes.
* @returns {boolean} - If true is returned the instrumented function body is executed, otherwise the
* uninstrumented function body is executed.
*/
this.runInstrumentedFunctionBody = function (iid, f, functionIid, functionSid) {
return false;
};
/**
* onReady is useful if your analysis is running on node.js (i.e., via the direct.js or jalangi.js commands)
* and needs to complete some asynchronous initialization before the instrumented program starts. In such a
* case, once the initialization is complete, invoke the cb function to start execution of the instrumented
* program.
*
* Note that this callback is not useful in the browser, as Jalangi has no control over when the
* instrumented program runs there.
* @param cb
*/
this.onReady = function (cb) {
cb();
};
}
sandbox.analysis = new MyAnalysis();
})(J$);

26
src_bundle/index.mjs Normal file
View File

@@ -0,0 +1,26 @@
import wp from 'webpack';
import path from 'node:path'
const outputPath = path.resolve('./output/');
console.log(outputPath);
const compiler = wp({
entry:'./test_src/arithmetic.cjs',
mode: 'production',
optimization:{
mangleExports: false,
avoidEntryIife: true,
minimize: false
},
output: {
path: outputPath,
filename: 'lodash.bundle.js',
scriptType: 'module',
}
,
},(err,stats)=>{
if (err || stats.hasErrors()) {
console.log(stats.hasErrors())
console.log(err?.stack)
console.log(stats.toJson());
}
})

10
src_dataset/index.mjs Normal file
View File

@@ -0,0 +1,10 @@
import packages from 'download-counts' assert { type: 'json'}
import * as csv from 'csv'
import fsp from 'fs/promises'
const packageList = Object.keys(packages).map(e => [e, packages[e]]).filter(e=>e[1]>500000).sort((e,f)=>(f[1]-e[1]));
const packageMap = new Map(packageList)
console.log(packageMap.size)
const output = csv.stringify(packageList)
await fsp.writeFile('output.csv',output);

7
test_src/arithmetic.cjs Normal file
View File

@@ -0,0 +1,7 @@
module.exports.sum = function sum(a, b) {
return a + b;
}
module.exports.div = function div(a, b) {
if (b == 0) return NaN;
return a / b;
}

24
test_src/index.cjs Normal file
View File

@@ -0,0 +1,24 @@
var {existsSync,readFile} = require('node:fs');
// const {cwd} = require('process');
var _var = require('process');
var {sum, div} = require('./arithmetic.cjs');
var {sum, div} = require('../output/lodash.bundle.js');
var {ceil} = require('./lodash.js')
let cwd = process.cwd;
console.log('a')
function x(){
return {x:3};
}
// readFile('./package.json',(data)=>{
// });
let newLocal = existsSync("./package.json");
console.log(`Read some data`,newLocal,sum(2,34));
console.log(`Read some data`,newLocal,
div(7,0),
div(32,3),
ceil(10.24)
);

17112
test_src/lodash.js Normal file

File diff suppressed because it is too large Load Diff

2
todo Normal file
View File

@@ -0,0 +1,2 @@
Handle aliasing as a important piece in draft
Please note down problem exploration