@ -1,4 +1,4 @@
import React , { useEffect , useState , useReducer } from 'react' // eslint-disable-line
import React , { useEffect , useState , useReducer , useRef } from 'react' // eslint-disable-line
import Button from './Button/StaticAnalyserButton' // eslint-disable-line
import { util } from '@remix-project/remix-lib'
import _ from 'lodash'
@ -64,10 +64,12 @@ export const RemixUiStaticAnalyser = (props: RemixUiStaticAnalyserProps) => {
const [ autoRun , setAutoRun ] = useState ( true )
const [ slitherEnabled , setSlitherEnabled ] = useState ( false )
const [ showSlither , setShowSlither ] = useState ( false )
let [ showLibsWarning , setShowLibsWarning ] = useState ( false ) // eslint-disable-line prefer-const
const [ categoryIndex , setCategoryIndex ] = useState ( groupedModuleIndex ( groupedModules ) )
const warningContainer = React . useRef ( null )
const [ warningState , setWarningState ] = useState ( { } )
const warningContainer = useRef ( null )
const allWarnings = useRef ( { } )
const [ state , dispatch ] = useReducer ( analysisReducer , initialState )
useEffect ( ( ) = > {
@ -76,9 +78,12 @@ export const RemixUiStaticAnalyser = (props: RemixUiStaticAnalyserProps) => {
useEffect ( ( ) = > {
setWarningState ( { } )
const runAnalysis = async ( ) = > {
await run ( state . data , state . source , state . file )
}
if ( autoRun ) {
if ( state . data !== null ) {
run ( state . data , state . source , state . file )
runAnalysis ( ) . catch ( console . error ) ;
}
} else {
props . event . trigger ( 'staticAnaysisWarning' , [ ] )
@ -131,6 +136,30 @@ export const RemixUiStaticAnalyser = (props: RemixUiStaticAnalyserProps) => {
)
}
const filterWarnings = ( ) = > {
let newWarningState = { }
let newWarningCount = 0
if ( showLibsWarning ) {
for ( const category in allWarnings . current )
newWarningCount = newWarningCount + allWarnings . current [ category ] . length
newWarningState = allWarnings . current
}
else {
for ( const category in allWarnings . current ) {
const warnings = allWarnings . current [ category ]
newWarningState [ category ] = [ ]
for ( const warning of warnings ) {
if ( ! warning . options . isLibrary ) {
newWarningCount ++
newWarningState [ category ] . push ( warning )
}
}
}
}
props . event . trigger ( 'staticAnaysisWarning' , [ newWarningCount ] )
setWarningState ( newWarningState )
}
const showWarnings = ( warningMessage , groupByKey ) = > {
const resultArray = [ ]
warningMessage . map ( x = > {
@ -149,143 +178,149 @@ export const RemixUiStaticAnalyser = (props: RemixUiStaticAnalyserProps) => {
}
const groupedCategory = groupBy ( resultArray , groupByKey )
setWarningState ( groupedCategory )
allWarnings . current = groupedCategory
filterWarnings ( )
}
const run = ( lastCompilationResult , lastCompilationSource , currentFile ) = > {
const run = async ( lastCompilationResult , lastCompilationSource , currentFile ) = > {
if ( state . data !== null ) {
if ( lastCompilationResult && ( categoryIndex . length > 0 || slitherEnabled ) ) {
let warningCount = 0
const warningMessage = [ ]
const warningErrors = [ ]
// Remix Analysis
_paq . push ( [ 'trackEvent' , 'solidityStaticAnalyzer' , 'analyzeWithRemixAnalyzer' ] )
runner . run ( lastCompilationResult , categoryIndex , results = > {
results . map ( ( result ) = > {
let module Name
Object . keys ( groupedModules ) . map ( key = > {
groupedModules [ key ] . forEach ( el = > {
if ( el . name === result . name ) {
module Name = groupedModules [ key ] [ 0 ] . categoryDisplayName
}
} )
} )
result . report . map ( ( item ) = > {
let location : any = { }
let locationString = 'not available'
let column = 0
let row = 0
let fileName = currentFile
if ( item . location ) {
const split = item . location . split ( ':' )
const file = split [ 2 ]
location = {
start : parseInt ( split [ 0 ] ) ,
length : parseInt ( split [ 1 ] )
}
location = props . analysisModule . _deps . offsetToLineColumnConverter . offsetToLineColumn (
location ,
parseInt ( file ) ,
lastCompilationSource . sources ,
lastCompilationResult . sources
)
row = location . start . line
column = location . start . column
locationString = row + 1 + ':' + column + ':'
fileName = Object . keys ( lastCompilationResult . sources ) [ file ]
const results = runner . run ( lastCompilationResult , categoryIndex )
for ( const result of results ) {
let module Name
Object . keys ( groupedModules ) . map ( key = > {
groupedModules [ key ] . forEach ( el = > {
if ( el . name === result . name ) {
module Name = groupedModules [ key ] [ 0 ] . categoryDisplayName
}
warningCount ++
const msg = message ( result . name , item . warning , item . more , fileName , locationString )
const options = {
type : 'warning' ,
useSpan : true ,
errFile : fileName ,
fileName ,
errLine : row ,
errCol : column ,
item : item ,
name : result.name ,
locationString ,
more : item.more ,
location : location
}
warningErrors . push ( options )
warningMessage . push ( { msg , options , hasWarning : true , warningModuleName : moduleName } )
} )
} )
// Slither Analysis
if ( slitherEnabled ) {
props . analysisModule . call ( 'solidity' , 'getCompilerState' ) . then ( ( compilerState ) = > {
const { currentVersion , optimize , evmVersion } = compilerState
props . analysisModule . call ( 'terminal' , 'log' , { type : 'info' , value : '[Slither Analysis]: Running...' } )
_paq . push ( [ 'trackEvent' , 'solidityStaticAnalyzer' , 'analyzeWithSlither' ] )
props . analysisModule . call ( 'slither' , 'analyse' , state . file , { currentVersion , optimize , evmVersion } ) . then ( async ( result ) = > {
if ( result . status ) {
props . analysisModule . call ( 'terminal' , 'log' , { type : 'info' , value : ` [Slither Analysis]: Analysis Completed!! ${ result . count } warnings found. ` } )
const report = result . data
for ( const item of report ) {
let location : any = { }
let locationString = 'not available'
let column = 0
let row = 0
let fileName = currentFile
for ( const item of result . report ) {
let location : any = { }
let locationString = 'not available'
let column = 0
let row = 0
let fileName = currentFile
let isLibrary = false
if ( item . sourceMap && item . sourceMap . length ) {
let path = item . sourceMap [ 0 ] . source_mapping . filename_relative
let fileIndex = Object . keys ( lastCompilationResult . sources ) . indexOf ( path )
if ( fileIndex === - 1 ) {
path = await props . analysisModule . call ( 'fileManager' , 'getUrlFromPath' , path )
fileIndex = Object . keys ( lastCompilationResult . sources ) . indexOf ( path . file )
}
if ( fileIndex >= 0 ) {
location = {
start : item.sourceMap [ 0 ] . source_mapping . start ,
length : item.sourceMap [ 0 ] . source_mapping . length
}
location = props . analysisModule . _deps . offsetToLineColumnConverter . offsetToLineColumn (
location ,
fileIndex ,
lastCompilationSource . sources ,
lastCompilationResult . sources
)
row = location . start . line
column = location . start . column
locationString = row + 1 + ':' + column + ':'
fileName = Object . keys ( lastCompilationResult . sources ) [ fileIndex ]
}
}
warningCount ++
const msg = message ( item . title , item . description , item . more , fileName , locationString )
const options = {
type : 'warning' ,
useSpan : true ,
errFile : fileName ,
fileName ,
errLine : row ,
errCol : column ,
item : { warning : item.description } ,
name : item.title ,
locationString ,
more : item.more ,
location : location
if ( item . location ) {
const split = item . location . split ( ':' )
const file = split [ 2 ]
location = {
start : parseInt ( split [ 0 ] ) ,
length : parseInt ( split [ 1 ] )
}
location = props . analysisModule . _deps . offsetToLineColumnConverter . offsetToLineColumn (
location ,
parseInt ( file ) ,
lastCompilationSource . sources ,
lastCompilationResult . sources
)
row = location . start . line
column = location . start . column
locationString = row + 1 + ':' + column + ':'
fileName = Object . keys ( lastCompilationResult . sources ) [ file ]
}
if ( fileName !== currentFile ) {
const { file , provider } = await props . analysisModule . call ( 'fileManager' , 'getPathFromUrl' , fileName )
if ( file . startsWith ( '.deps' ) || ( provider . type === 'localhost' && file . startsWith ( 'localhost/node_modules' ) ) ) isLibrary = true
}
const msg = message ( result . name , item . warning , item . more , fileName , locationString )
const options = {
type : 'warning' ,
useSpan : true ,
errFile : fileName ,
fileName ,
isLibrary ,
errLine : row ,
errCol : column ,
item : item ,
name : result.name ,
locationString ,
more : item.more ,
location : location
}
warningErrors . push ( options )
warningMessage . push ( { msg , options , hasWarning : true , warningModuleName : moduleName } )
}
}
// Slither Analysis
if ( slitherEnabled ) {
try {
const compilerState = await props . analysisModule . call ( 'solidity' , 'getCompilerState' )
const { currentVersion , optimize , evmVersion } = compilerState
await props . analysisModule . call ( 'terminal' , 'log' , { type : 'info' , value : '[Slither Analysis]: Running...' } )
_paq . push ( [ 'trackEvent' , 'solidityStaticAnalyzer' , 'analyzeWithSlither' ] )
const result = await props . analysisModule . call ( 'slither' , 'analyse' , state . file , { currentVersion , optimize , evmVersion } )
if ( result . status ) {
props . analysisModule . call ( 'terminal' , 'log' , { type : 'info' , value : ` [Slither Analysis]: Analysis Completed!! ${ result . count } warnings found. ` } )
const report = result . data
for ( const item of report ) {
let location : any = { }
let locationString = 'not available'
let column = 0
let row = 0
let fileName = currentFile
let isLibrary = false
if ( item . sourceMap && item . sourceMap . length ) {
let path = item . sourceMap [ 0 ] . source_mapping . filename_relative
let fileIndex = Object . keys ( lastCompilationResult . sources ) . indexOf ( path )
if ( fileIndex === - 1 ) {
path = await props . analysisModule . call ( 'fileManager' , 'getUrlFromPath' , path )
fileIndex = Object . keys ( lastCompilationResult . sources ) . indexOf ( path . file )
}
if ( fileIndex >= 0 ) {
location = {
start : item.sourceMap [ 0 ] . source_mapping . start ,
length : item.sourceMap [ 0 ] . source_mapping . length
}
warningErrors . push ( options )
warningMessage . push ( { msg , options , hasWarning : true , warningModuleName : 'Slither Analysis' } )
location = props . analysisModule . _deps . offsetToLineColumnConverter . offsetToLineColumn (
location ,
fileIndex ,
lastCompilationSource . sources ,
lastCompilationResult . sources
)
row = location . start . line
column = location . start . column
locationString = row + 1 + ':' + column + ':'
fileName = Object . keys ( lastCompilationResult . sources ) [ fileIndex ]
}
showWarnings ( warningMessage , 'warningModuleName' )
props . event . trigger ( 'staticAnaysisWarning' , [ warningCount ] )
}
} ) . catch ( ( ) = > {
props . analysisModule . call ( 'terminal' , 'log' , { type : 'error' , value : '[Slither Analysis]: Error occured! See remixd console for details.' } )
showWarnings ( warningMessage , 'warningModuleName' )
} )
} )
} else {
if ( fileName !== currentFile ) {
const { file , provider } = await props . analysisModule . call ( 'fileManager' , 'getPathFromUrl' , fileName )
if ( file . startsWith ( '.deps' ) || ( provider . type === 'localhost' && file . startsWith ( 'localhost/node_modules' ) ) ) isLibrary = true
}
const msg = message ( item . title , item . description , item . more , fileName , locationString )
const options = {
type : 'warning' ,
useSpan : true ,
errFile : fileName ,
fileName ,
isLibrary ,
errLine : row ,
errCol : column ,
item : { warning : item.description } ,
name : item.title ,
locationString ,
more : item.more ,
location : location
}
warningErrors . push ( options )
warningMessage . push ( { msg , options , hasWarning : true , warningModuleName : 'Slither Analysis' } )
}
showWarnings ( warningMessage , 'warningModuleName' )
}
} catch ( error ) {
props . analysisModule . call ( 'terminal' , 'log' , { type : 'error' , value : '[Slither Analysis]: Error occured! See remixd console for details.' } )
showWarnings ( warningMessage , 'warningModuleName' )
props . event . trigger ( 'staticAnaysisWarning' , [ warningCount ] )
}
} )
} else showWarnings ( warningMessage , 'warningModuleName' )
} else {
if ( categoryIndex . length ) {
warningContainer . current . innerText = 'No compiled AST available'
@ -346,6 +381,17 @@ export const RemixUiStaticAnalyser = (props: RemixUiStaticAnalyserProps) => {
}
}
const handleShowLibsWarning = ( ) = > {
if ( showLibsWarning ) {
showLibsWarning = false
setShowLibsWarning ( false )
} else {
showLibsWarning = true
setShowLibsWarning ( true )
}
filterWarnings ( )
}
const categoryItem = ( categoryId , item , i ) = > {
return (
< div className = "form-check" key = { i } >
@ -409,6 +455,7 @@ export const RemixUiStaticAnalyser = (props: RemixUiStaticAnalyserProps) => {
< RemixUiCheckbox
id = "checkAllEntries"
inputType = "checkbox"
title = "Select all Remix analysis modules"
checked = { Object . values ( groupedModules ) . map ( ( value : any ) = > {
return ( value . map ( x = > {
return x . _index . toString ( )
@ -421,12 +468,13 @@ export const RemixUiStaticAnalyser = (props: RemixUiStaticAnalyserProps) => {
< RemixUiCheckbox
id = "autorunstaticanalysis"
inputType = "checkbox"
title = "Run static analysis after the compilation"
onClick = { handleAutoRun }
checked = { autoRun }
label = "Autorun"
onChange = { ( ) = > { } }
/ >
< Button buttonText = "Run" onClick = { ( ) = > run ( state . data , state . source , state . file ) } disabled = { ( state . data === null || categoryIndex . length === 0 ) && ! slitherEnabled } / >
< Button buttonText = "Run" onClick = { async ( ) = > await run ( state . data , state . source , state . file ) } disabled = { ( state . data === null || categoryIndex . length === 0 ) && ! slitherEnabled } / >
< / div >
{ showSlither &&
< div className = "d-flex mt-2" id = "enableSlitherAnalysis" >
@ -469,14 +517,25 @@ export const RemixUiStaticAnalyser = (props: RemixUiStaticAnalyserProps) => {
{ state . file }
< / span >
< / div >
< br / >
{ Object . entries ( warningState ) . length > 0 &&
< div id = 'staticanalysisresult' >
< RemixUiCheckbox
id = "showLibWarnings"
name = "showLibWarnings"
categoryId = "showLibWarnings"
title = "when checked, the results are also displayed for external contract libraries"
inputType = "checkbox"
checked = { showLibsWarning }
label = "Display all results"
onClick = { handleShowLibsWarning }
onChange = { ( ) = > { } }
/ >
< br / >
< div className = "mb-4" >
{
( Object . entries ( warningState ) . map ( ( element , index ) = > (
< div key = { index } >
< span className = "text-dark h6" > { element [ 0 ] } < / span >
{ element [ 1 ] [ 'length' ] > 0 ? < span className = "text-dark h6" > { element [ 0 ] } < / span > : null }
{ element [ 1 ] [ 'map' ] ( ( x , i ) = > ( // eslint-disable-line dot-notation
x . hasWarning ? ( // eslint-disable-next-line dot-notation
< div data - id = { ` staticAnalysisModule ${ x . warningModuleName } ${ i } ` } id = { ` staticAnalysisModule ${ x . warningModuleName } ${ i } ` } key = { i } >