Skip to content

Commit

Permalink
Merge pull request #4925 from ethereum/desktopslither2
Browse files Browse the repository at this point in the history
Desktopslither2
  • Loading branch information
bunsenstraat committed Jun 27, 2024
2 parents 30564ed + 50ee5e0 commit 3a38cd0
Show file tree
Hide file tree
Showing 13 changed files with 423 additions and 10 deletions.
9 changes: 7 additions & 2 deletions apps/remix-ide/src/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@ import { xtermPlugin } from './app/plugins/electron/xtermPlugin'
import { ripgrepPlugin } from './app/plugins/electron/ripgrepPlugin'
import { compilerLoaderPlugin, compilerLoaderPluginDesktop } from './app/plugins/electron/compilerLoaderPlugin'
import { appUpdaterPlugin } from './app/plugins/electron/appUpdaterPlugin'
import { SlitherHandleDesktop } from './app/plugins/electron/slitherPlugin'
import { SlitherHandle } from './app/files/slither-handle'
import {SolCoder} from './app/plugins/solcoderAI'

const isElectron = require('is-electron')
Expand Down Expand Up @@ -383,6 +385,10 @@ class AppComponent {
const compilerloader = isElectron()? new compilerLoaderPluginDesktop(): new compilerLoaderPlugin()
this.engine.register([compilerloader])

// slither analyzer plugin (remixd / desktop)
const slitherPlugin = isElectron() ? new SlitherHandleDesktop() : new SlitherHandle()
this.engine.register([slitherPlugin])

// LAYOUT & SYSTEM VIEWS
const appPanel = new MainPanel()
Registry.getInstance().put({api: this.mainview, name: 'mainview'})
Expand Down Expand Up @@ -440,7 +446,6 @@ class AppComponent {
filePanel.hardhatHandle,
filePanel.foundryHandle,
filePanel.truffleHandle,
filePanel.slitherHandle,
linkLibraries,
deployLibraries,
openZeppelinProxy,
Expand Down Expand Up @@ -507,7 +512,7 @@ class AppComponent {
await this.appManager.activatePlugin(['solidity-script', 'remix-templates'])

if (isElectron()){
await this.appManager.activatePlugin(['isogit', 'electronconfig', 'electronTemplates', 'xterm', 'ripgrep', 'appUpdater'])
await this.appManager.activatePlugin(['isogit', 'electronconfig', 'electronTemplates', 'xterm', 'ripgrep', 'appUpdater', 'slither'])
}

this.appManager.on(
Expand Down
2 changes: 0 additions & 2 deletions apps/remix-ide/src/app/panels/file-panel.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import {PluginViewWrapper} from '@remix-ui/helper'
const { HardhatHandle } = require('../files/hardhat-handle.js')
const { FoundryHandle } = require('../files/foundry-handle.js')
const { TruffleHandle } = require('../files/truffle-handle.js')
const { SlitherHandle } = require('../files/slither-handle.js')

/*
Overview of APIs:
Expand Down Expand Up @@ -72,7 +71,6 @@ module.exports = class Filepanel extends ViewPlugin {
this.hardhatHandle = new HardhatHandle()
this.foundryHandle = new FoundryHandle()
this.truffleHandle = new TruffleHandle()
this.slitherHandle = new SlitherHandle()
this.contentImport = contentImport
this.workspaces = []
this.appManager = appManager
Expand Down
13 changes: 13 additions & 0 deletions apps/remix-ide/src/app/plugins/electron/slitherPlugin.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { ElectronPlugin } from '@remixproject/engine-electron';

export class SlitherHandleDesktop extends ElectronPlugin {
constructor() {
super({
displayName: 'slither',
name: 'slither',
description: 'electron slither',
methods: ['analyse']
})
this.methods = ['analyse']
}
}
2 changes: 1 addition & 1 deletion apps/remix-ide/src/remixAppManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ export class RemixAppManager extends PluginManager {
this.pluginsDirectory = 'https://github.com/ethereum/remix-plugins-directory/master/build/metadata.json'
this.pluginLoader = new PluginLoader()
if (Registry.getInstance().get('platform').api.isDesktop()) {
requiredModules = [...requiredModules, 'fs', 'electronTemplates', 'isogit', 'remix-templates', 'electronconfig', 'xterm', 'compilerloader', 'ripgrep']
requiredModules = [...requiredModules, 'fs', 'electronTemplates', 'isogit', 'remix-templates', 'electronconfig', 'xterm', 'compilerloader', 'ripgrep', 'slither']
}
}

Expand Down
3 changes: 3 additions & 0 deletions apps/remixdesktop/src/engine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { ConfigPlugin } from './plugins/configPlugin';
import { TemplatesPlugin } from './plugins/templates';
import { RipgrepPlugin } from './plugins/ripgrepPlugin';
import { CompilerLoaderPlugin } from './plugins/compilerLoader';
import { SlitherPlugin } from './plugins/slitherPlugin';
import { AppUpdaterPlugin } from './plugins/appUpdater';

const engine = new Engine()
Expand All @@ -20,6 +21,7 @@ const configPlugin = new ConfigPlugin()
const templatesPlugin = new TemplatesPlugin()
const ripgrepPlugin = new RipgrepPlugin()
const compilerLoaderPlugin = new CompilerLoaderPlugin()
const slitherPlugin = new SlitherPlugin()
const appUpdaterPlugin = new AppUpdaterPlugin()

engine.register(appManager)
Expand All @@ -30,6 +32,7 @@ engine.register(configPlugin)
engine.register(templatesPlugin)
engine.register(ripgrepPlugin)
engine.register(compilerLoaderPlugin)
engine.register(slitherPlugin)
engine.register(appUpdaterPlugin)

appManager.activatePlugin('electronconfig')
Expand Down
36 changes: 36 additions & 0 deletions apps/remixdesktop/src/lib/remixd.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { ElectronBasePluginClient } from "@remixproject/plugin-electron";
import { Profile } from "@remixproject/plugin-utils";

export class ElectronBasePluginRemixdClient extends ElectronBasePluginClient {
log: (...message: any) => void
error: (...message: any) => void

currentSharedFolder: string = ''
constructor(webContentsId: number, profile: Profile) {
super(webContentsId, profile);
this.log = (...message: any) => {
for(const m of message) {
this.call('terminal', 'log', {
type: 'log',
value: m
})
}
}
this.error = (...message: any) => {
for(const m of message) {
this.call('terminal', 'log', {
type: 'error',
value: m
})
}
}


this.onload(async () => {
this.on('fs' as any, 'workingDirChanged', async (path: string) => {
this.currentSharedFolder = path
})
this.currentSharedFolder = await this.call('fs' as any, 'getWorkingDir')
})
}
}
193 changes: 193 additions & 0 deletions apps/remixdesktop/src/lib/slither.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
import { existsSync, readFileSync, readdirSync, unlinkSync } from 'fs'
import * as utils from './utils'
const { spawn, execSync } = require('child_process') // eslint-disable-line
export interface OutputStandard {
description: string
title: string
confidence: string
severity: string
sourceMap: any
category?: string
reference?: string
example?: any
[key: string]: any
}

export const SlitherClientMixin = (Base) => class extends Base {
methods: Array<string>
currentSharedFolder: string

constructor(...args: any[]) {
super(...args); // Ensure the parent constructor is called
}


log(...message: any) {
if (this.log) {
this.log(...message)
} else {
console.log(...message)
}
}

error(...message: any) {
if (this.error) {
this.error(...message)
} else {
console.error(...message)
}
}


mapNpmDepsDir(list) {
const remixNpmDepsPath = utils.absolutePath('.deps/npm', this.currentSharedFolder)
const localNpmDepsPath = utils.absolutePath('node_modules', this.currentSharedFolder)
const npmDepsExists = existsSync(remixNpmDepsPath)
const nodeModulesExists = existsSync(localNpmDepsPath)
let isLocalDep = false
let isRemixDep = false
let allowPathString = ''
let remapString = ''

for (const e of list) {
const importPath = e.replace(/import ['"]/g, '').trim()
const packageName = importPath.split('/')[0]
if (nodeModulesExists && readdirSync(localNpmDepsPath).includes(packageName)) {
isLocalDep = true
remapString += `${packageName}=./node_modules/${packageName} `
} else if (npmDepsExists && readdirSync(remixNpmDepsPath).includes(packageName)) {
isRemixDep = true
remapString += `${packageName}=./.deps/npm/${packageName} `
}
}
if (isLocalDep) allowPathString += './node_modules,'
if (isRemixDep) allowPathString += './.deps/npm,'

return { remapString, allowPathString }
}

transform(detectors: Record<string, any>[]): OutputStandard[] {
const standardReport: OutputStandard[] = []
for (const e of detectors) {
const obj = {} as OutputStandard
obj.description = e.description
obj.title = e.check
obj.confidence = e.confidence
obj.severity = e.impact
obj.sourceMap = e.elements.map((element) => {
delete element.source_mapping.filename_used
delete element.source_mapping.filename_absolute
return element
})
standardReport.push(obj)
}
return standardReport
}

analyse(filePath: string, compilerConfig: Record<string, any>) {
return new Promise((resolve, reject) => {
const options = { cwd: this.currentSharedFolder, shell: true }

const { currentVersion, optimize, evmVersion } = compilerConfig
if (currentVersion && currentVersion.includes('+commit')) {
// Get compiler version with commit id e.g: 0.8.2+commit.661d110
const versionString: string = currentVersion.substring(0, currentVersion.indexOf('+commit') + 16)
this.log(`[Slither Analysis]: Compiler version is ${versionString}`)
let solcOutput: Buffer
// Check solc current installed version
try {
solcOutput = execSync('solc --version', options)
} catch (err) {
this.error(err)
reject(new Error('Error in running solc command'))
}
if (!solcOutput.toString().includes(versionString)) {
this.log('[Slither Analysis]: Compiler version is different from installed solc version')
// Get compiler version without commit id e.g: 0.8.2
const version: string = versionString.substring(0, versionString.indexOf('+commit'))
// List solc versions installed using solc-select
try {
const solcSelectInstalledVersions: Buffer = execSync('solc-select versions', options)
// Check if required version is already installed
if (!solcSelectInstalledVersions.toString().includes(version)) {
this.log(`[Slither Analysis]: Installing ${version} using solc-select`)
// Install required version
execSync(`solc-select install ${version}`, options)
}
this.log(`[Slither Analysis]: Setting ${version} as current solc version using solc-select`)
// Set solc current version as required version
execSync(`solc-select use ${version}`, options)
} catch (err) {
this.error(err)
reject(new Error('Error in running solc-select command'))
}
} else this.log('[Slither Analysis]: Compiler version is same as installed solc version')
}
// Allow paths and set solc remapping for import URLs
const fileContent = readFileSync(utils.absolutePath(filePath, this.currentSharedFolder), 'utf8')
const importsArr = fileContent.match(/import ['"][^.|..](.+?)['"];/g)
let remaps = ''
if (importsArr?.length) {
const { remapString } = this.mapNpmDepsDir(importsArr)
remaps = remapString.trim()
}
const optimizeOption: string = optimize ? '--optimize' : ''
const evmOption: string = evmVersion ? `--evm-version ${evmVersion}` : ''
let solcArgs = ''
if (optimizeOption) {
solcArgs += optimizeOption + ' '
}
if (evmOption) {
if (!solcArgs.endsWith(' ')) solcArgs += ' '
solcArgs += evmOption
}
if (solcArgs) {
solcArgs = `--solc-args "${solcArgs.trimStart()}"`
}
const solcRemaps = remaps ? `--solc-remaps "${remaps}"` : ''

const outputFile = 'remix-slither-report.json'
try {
// We don't keep the previous analysis
const outputFilePath = utils.absolutePath(outputFile, this.currentSharedFolder)
if (existsSync(outputFilePath)) unlinkSync(outputFilePath)
} catch (e) {
this.error('unable to remove the output file')
this.error(e.message)
}
const cmd = `slither ${filePath} ${solcArgs} ${solcRemaps} --json ${outputFile}`
this.log('[Slither Analysis]: Running Slither...')
// Added `stdio: 'ignore'` as for contract with NPM imports analysis which is exported in 'stderr'
// get too big and hangs the process. We process analysis from the report file only
const child = spawn(cmd, { cwd: this.currentSharedFolder, shell: true, stdio: 'ignore' })

const response = {}
child.on('close', () => {
const outputFileAbsPath: string = utils.absolutePath(outputFile, this.currentSharedFolder)
// Check if slither report file exists
if (existsSync(outputFileAbsPath)) {
let report = readFileSync(outputFileAbsPath, 'utf8')
report = JSON.parse(report)
if (report['success']) {
response['status'] = true
if (!report['results'] || !report['results'].detectors || !report['results'].detectors.length) {
response['count'] = 0
} else {
const { detectors } = report['results']
response['count'] = detectors.length
response['data'] = this.transform(detectors)
}

resolve(response)
} else {
this.log(report['error'])
reject(new Error('Error in running Slither Analysis.'))
}
} else {
this.error('Error in generating Slither Analysis Report. Make sure Slither is properly installed.')
reject(new Error('Error in generating Slither Analysis Report. Make sure Slither is properly installed.'))
}
})
})
}
}
24 changes: 24 additions & 0 deletions apps/remixdesktop/src/lib/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import * as pathModule from 'path'
/**
* returns the absolute path of the given @arg path
*
* @param {String} path - relative path (Unix style which is the one used by Remix IDE)
* @param {String} sharedFolder - absolute shared path. platform dependent representation.
* @return {String} platform dependent absolute path (/home/user1/.../... for unix, c:\user\...\... for windows)
*/
function absolutePath (path: string, sharedFolder:string): string {
path = normalizePath(path)
path = pathModule.resolve(sharedFolder, path)
return path
}
function normalizePath (path) {
if (path === '/') path = './'
if (process.platform === 'win32') {
return path.replace(/\//g, '\\')
}
return path
}

export { absolutePath }


34 changes: 34 additions & 0 deletions apps/remixdesktop/src/plugins/slitherPlugin.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { Profile } from "@remixproject/plugin-utils";
import { ElectronBasePlugin, ElectronBasePluginClient } from "@remixproject/plugin-electron"

import { ElectronBasePluginRemixdClient } from "../lib/remixd"
import { SlitherClientMixin } from "../lib/slither";
const profile: Profile = {
name: 'slither',
displayName: 'electron slither',
description: 'electron slither',
}

export class SlitherPlugin extends ElectronBasePlugin {
clients: any []
constructor() {
super(profile, clientProfile, SlitherClientMixin(SlitherPluginClient))
this.methods = [...super.methods]
}
}

const clientProfile: Profile = {
name: 'slither',
displayName: 'electron slither',
description: 'electron slither',
methods: ['analyse']
}


class SlitherPluginClient extends ElectronBasePluginRemixdClient {
constructor(webContentsId: number, profile: Profile) {
super(webContentsId, profile);
}
}


Loading

0 comments on commit 3a38cd0

Please sign in to comment.