Skip to content

Commit

Permalink
function sigs and uml rocks
Browse files Browse the repository at this point in the history
  • Loading branch information
tintinweb committed Sep 12, 2019
1 parent 57ba0b9 commit 9251c84
Show file tree
Hide file tree
Showing 6 changed files with 242 additions and 6 deletions.
10 changes: 10 additions & 0 deletions package.json
Expand Up @@ -243,6 +243,16 @@
"type": "boolean",
"default": true,
"description": "Enable/Disable all active components of this extension (emergency)."
},
"solidity-va.uml.options": {
"type": "text",
"default": "",
"description": "Add custom uml options"
},
"solidity-va.uml.actors.enable": {
"type": "boolean",
"default": false,
"description": "Enable/Disable actors in uml"
}
}
}
Expand Down
24 changes: 24 additions & 0 deletions src/extension.js
Expand Up @@ -718,6 +718,15 @@ function onActivate(context) {
)
)

context.subscriptions.push(
vscode.commands.registerCommand(
'solidity-va.tools.function.signatureForAstItem',
function (item) {
commands.listFunctionSignatureForAstItem(item)
}
)
)

context.subscriptions.push(
vscode.commands.registerCommand(
'solidity-va.tools.remix.openExternal',
Expand All @@ -727,6 +736,15 @@ function onActivate(context) {
)
)

context.subscriptions.push(
vscode.commands.registerCommand(
'solidity-va.uml.contract.outline',
function (doc, contractObjects) {
commands.umlContractsOutline(contractObjects)
}
)
)

/** event setup */
/***** DidChange */
vscode.window.onDidChangeActiveTextEditor(editor => {
Expand All @@ -746,6 +764,12 @@ function onActivate(context) {
onDidSave(document);
}, null, context.subscriptions);

/****** OnOpen */
vscode.workspace.onDidOpenTextDocument(document => {
onDidSave(document);
}, null, context.subscriptions);


context.subscriptions.push(
vscode.languages.registerHoverProvider(type, {
provideHover(document, position, token) {
Expand Down
22 changes: 22 additions & 0 deletions src/features/codelens.js
Expand Up @@ -111,6 +111,13 @@ class SolidityCodeLensProvider {
)
)

codeLens.push(new vscode.CodeLens(firstLine, {
command: 'solidity-va.uml.contract.outline',
title: 'uml',
arguments: [document, Object.values(parser.contracts)]
})
)

let annotateContractTypes = ["contract","library"]
/** all contract decls */
for(let name in parser.contracts){
Expand Down Expand Up @@ -147,6 +154,13 @@ class SolidityCodeLensProvider {
arguments: [document, item.name, []]
})
)

lenses.push(new vscode.CodeLens(range, {
command: 'solidity-va.uml.contract.outline',
title: 'uml',
arguments: [document, [item]]
})
)

return lenses
}
Expand All @@ -161,6 +175,14 @@ class SolidityCodeLensProvider {
arguments: [document, contractName+"::"+item._node.name, "all", document.uri.path]
})
)

lenses.push(new vscode.CodeLens(range, {
command: 'solidity-va.tools.function.signatureForAstItem',
title: 'funcSig',
arguments: [item]
})
)

return lenses
}
}
Expand Down
177 changes: 175 additions & 2 deletions src/features/commands.js
Expand Up @@ -15,6 +15,7 @@ const settings = require('../settings')

const mod_templates = require('./templates');
const mod_utils = require('./utils.js')
const mod_symbols = require('./symbols.js')

const surya = require('surya')

Expand Down Expand Up @@ -410,7 +411,13 @@ ${topLevelContractsText}`
}

async listFunctionSignatures(document, asJson){
let sighashes = mod_utils.functionSignatureExtractor(document.getText())
let sighash_colls = mod_utils.functionSignatureExtractor(document.getText())
let sighashes = sighash_colls.sighashes;

if(sighash_colls.collisions){
vscode.window.showErrorMessage('🔥 FuncSig collisions detected! ' + sighash_colls.collisions.join(","))
}

let content
if(asJson){
content = JSON.stringify(sighashes)
Expand All @@ -419,6 +426,11 @@ ${topLevelContractsText}`
for(let hash in sighashes){
content += hash + " => " + sighashes[hash] + "\n"
}
if(sighash_colls.collisions){
content += "\n\n";
content += "collisions 🔥🔥🔥 \n========================\n"
content += sighash_colls.collisions.join("\n");
}
}
vscode.workspace.openTextDocument({content: content, language: "markdown"})
.then(doc => vscode.window.showTextDocument(doc, vscode.ViewColumn.Beside))
Expand All @@ -427,19 +439,27 @@ ${topLevelContractsText}`
async listFunctionSignaturesForWorkspace(asJson){

let sighashes = {}
let collisions = []

await vscode.workspace.findFiles("**/*.sol",'**/node_modules', 500)
.then(uris => {
uris.forEach(uri => {
try {
let currSigs = mod_utils.functionSignatureExtractor(fs.readFileSync(uri.path).toString('utf-8'));
let sig_colls = mod_utils.functionSignatureExtractor(fs.readFileSync(uri.path).toString('utf-8'));
collisions = collisions.concat(sig_colls.collisions) //we're not yet checking sighash collisions across contracts

let currSigs = sig_colls.sighashes;
for(let k in currSigs){
sighashes[k]=currSigs[k]
}
} catch {}
})
})

if(collisions){
vscode.window.showErrorMessage('🔥 FuncSig collisions detected! ' + collisions.join(","))
}

let content
if(asJson){
content = JSON.stringify(sighashes)
Expand All @@ -448,10 +468,163 @@ ${topLevelContractsText}`
for(let hash in sighashes){
content += hash + " => " + sighashes[hash] + " \n"
}
if(collisions){
content += "\n\n";
content += "collisions 🔥🔥🔥 \n========================\n"
content += collisions.join("\n");
}
}
vscode.workspace.openTextDocument({content: content, language: "markdown"})
.then(doc => vscode.window.showTextDocument(doc, vscode.ViewColumn.Beside))
}

async listFunctionSignatureForAstItem(item, asJson){

let sighashes = mod_utils.functionSignatureFromAstNode(item);

let content
if(asJson){
content = JSON.stringify(sighashes)
} else {
content = "Sighash | Function Signature\n======================== \n"
for(let hash in sighashes){
content += hash + " => " + sighashes[hash] + " \n"
}
}
vscode.workspace.openTextDocument({content: content, language: "markdown"})
.then(doc => vscode.window.showTextDocument(doc, vscode.ViewColumn.Beside))
}

async umlContractsOutline(contractObjects){
const ENABLE_ACTORS = false;

const stateMutabilityToIcon = {
view:"🔍",
pure:"🔍",
constant:"🔍",
payable:"💰"
}

const functionVisibility = {
"public": '+',
"external": '+', //~
"internal": '#', //mapped to protected; ot
"private": '-' ,
"default": '+' // public
}
const variableVisibility = {
"public": '+',
"external": '+', //~
"internal": '#', //mapped to protected; ot
"private": '-' ,
"default": "#" // internal
}
const contractNameMapping = {
"contract":"class",
"interface":"interface",
"library":"abstract"
}

function _mapAstFunctionName(name) {
switch(name) {
case null:
return "**__constructor__**";
case "":
return "**__fallback__**";
default:
return name;
}
}

let content = `@startuml
' -- for auto-render install: https://marketplace.visualstudio.com/items?itemName=jebbs.plantuml
' -- options --
${solidityVAConfig.uml.options}
${solidityVAConfig.uml.actors.enable ? "allowmixing": ""}
' -- classes --
`

content += contractObjects.reduce((umlTxt, contractObj) => {

return umlTxt + `\n
${contractNameMapping[contractObj._node.kind] || "class"} ${contractObj.name} {
' -- inheritance --
${Object.values(contractObj.dependencies).reduce((txt, name) => {
return txt + `\t{abstract}${name}\n`
},"")
}
' -- usingFor --
${Object.values(contractObj.usingFor).reduce((txt, astNode) => {
return txt + `\t{abstract}📚${astNode.libraryName} for [[${mod_symbols.getVariableDeclarationType(astNode)}]]\n`
},"")
}
' -- vars --
${Object.values(contractObj.stateVars).reduce((umlSvarTxt, astNode) => {
return umlSvarTxt + `\t${variableVisibility[astNode.visibility] || ""}${astNode.isDeclaredConst?"{static}":""}[[${mod_symbols.getVariableDeclarationType(astNode).replace(/\(/g,"").replace(/\)/g,"")}]] ${astNode.name}\n`
}, "")
}
' -- methods --
${Object.values(contractObj.functions).reduce((umlFuncTxt, funcObj) => {
return umlFuncTxt + `\t${functionVisibility[funcObj._node.visibility] || ""}${stateMutabilityToIcon[funcObj._node.stateMutability]||""}${_mapAstFunctionName(funcObj._node.name)}()\n`
}, "")
}
}
`
}, "")

content += "' -- inheritance / usingFor --\n" + contractObjects.reduce((umlTxt, contractObj) => {
return umlTxt
+ Object.values(contractObj.dependencies).reduce((txt, name) => {
return txt + `${contractObj.name} <|--[#DarkGoldenRod] ${name}\n`
}, "")
+ Object.values(contractObj.usingFor).reduce((txt, astNode) => {
return txt + `${contractObj.name} <|..[#DarkOliveGreen] ${astNode.libraryName} : //for ${mod_symbols.getVariableDeclarationType(astNode)}//\n`
}, "")
}, "")


if(solidityVAConfig.uml.actors.enable){
//lets see if we can get actors as well :)

let addresses = []

for (let contractObj of contractObjects) {
addresses = addresses.concat(Object.values(contractObj.stateVars).filter(astNode => !astNode.isDeclaredConst && astNode.typeName.name =="address").map(astNode => astNode.name))
for (let fidx in contractObj.functions){
let functionObj = contractObj.functions[fidx]
addresses = addresses.concat(Object.values(functionObj.arguments).filter(astNode => astNode.typeName.name =="address").map(astNode => astNode.name))
}
}

let actors = [...new Set(addresses)]
actors = actors.filter( item => {
if (item === null) return false // no nulls
if (item.startsWith("_") && actors.indexOf(item.slice(1))) return false // no _<name> dupes
return true
})
console.error(actors)

content += `
' -- actors --
together {
${actors.reduce((txt, name) => txt + `\tactor ${name}\n`, "")}
}
`
}

content += "\n@enduml"

vscode.workspace.openTextDocument({content: content, language: "plantuml"})
.then(doc => vscode.window.showTextDocument(doc, vscode.ViewColumn.Beside)
.then(editor => {
vscode.commands.executeCommand("plantuml.preview")
.catch(error => {
//command does not exist
})
})
)
}
}


Expand Down
3 changes: 2 additions & 1 deletion src/features/symbols.js
Expand Up @@ -580,5 +580,6 @@ class SolidityDocumentSymbolProvider{


module.exports = {
SolidityDocumentSymbolProvider:SolidityDocumentSymbolProvider
SolidityDocumentSymbolProvider:SolidityDocumentSymbolProvider,
getVariableDeclarationType:getVariableDeclarationType
}
12 changes: 9 additions & 3 deletions src/features/utils.js
Expand Up @@ -66,16 +66,22 @@ function functionSignatureExtractor(content) {
const funcSigRegex = /function\s+(?<name>[^\(\s]+)\s?\((?<args>[^\)]*)\)/g
let match;
let sighashes = {}
let collisions = [];

while (match = funcSigRegex.exec(content)) {
let args = []
match.groups.args.split(",").forEach(item => {
args.push(canonicalizeEvmType(item.trim().split(" ")[0]))
})
let fnsig = `${match.groups.name.trim()}(${args.join(',')})`
sighashes[createKeccakHash('keccak256').update(fnsig).digest('hex').toString('hex').slice(0, 8)] = fnsig
let fnsig = `${match.groups.name.trim()}(${args.join(',')})`;
let sighash = createKeccakHash('keccak256').update(fnsig).digest('hex').toString('hex').slice(0, 8);

if(sighash in sighashes && sighashes[sighash]!==fnsig){
collisions.push(sighash)
}
sighashes[sighash] = fnsig
}
return sighashes
return {sighashes:sighashes, collisions:collisions}
}

function getCanonicalizedArgumentFromAstNode(node){
Expand Down

0 comments on commit 9251c84

Please sign in to comment.