addressed comments from @ryestew and @yann300

pull/3642/head
Joseph Izang 1 year ago
parent 014df35d3f
commit 8d50aa564a
  1. 33
      libs/remix-ui/static-analyser/src/lib/actions/staticAnalysisActions.ts
  2. 2
      libs/remix-ui/static-analyser/src/lib/components/BasicTitle.tsx
  3. 331
      libs/remix-ui/static-analyser/src/lib/remix-ui-static-analyser.tsx
  4. 53
      libs/remix-ui/static-analyser/src/staticanalyser.d.ts
  5. 6
      libs/remix-ui/vertical-icons-panel/src/lib/components/Badge.tsx

@ -1,6 +1,6 @@
import { CompilationResult, SourceWithTarget } from '@remixproject/plugin-api'
import React from 'react' //eslint-disable-line
import { AnalysisTab, RemixUiStaticAnalyserReducerActionType, RemixUiStaticAnalyserState, SolHintReport } from '../../staticanalyser'
import { AnalysisTab, RemixUiStaticAnalyserReducerActionType, RemixUiStaticAnalyserState, SolHintReport, SlitherAnalysisResults } from '../../staticanalyser'
import { RemixUiStaticAnalyserProps } from '@remix-ui/static-analyser'
/**
@ -42,11 +42,11 @@ export const compilation = (analysisModule: AnalysisTab,
* @returns {Promise<void>}
*/
// eslint-disable-next-line @typescript-eslint/no-unused-vars
export async function run (lastCompilationResult, lastCompilationSource, currentFile: string, state: RemixUiStaticAnalyserState, props: RemixUiStaticAnalyserProps, isSupportedVersion, slitherEnabled, categoryIndex: number[], groupedModules, runner, _paq, message, showWarnings, allWarnings: React.RefObject<any>, warningContainer: React.RefObject<any>, calculateWarningStateEntries: (e:[string, any][]) => {length: number, errors: any[] }, warningState, setHints: React.Dispatch<React.SetStateAction<SolHintReport[]>>, hints: SolHintReport[]) {
export async function run (lastCompilationResult, lastCompilationSource, currentFile: string, state: RemixUiStaticAnalyserState, props: RemixUiStaticAnalyserProps, isSupportedVersion, showSlither, categoryIndex: number[], groupedModules, runner, _paq, message, showWarnings, allWarnings: React.RefObject<any>, warningContainer: React.RefObject<any>, calculateWarningStateEntries: (e:[string, any][]) => {length: number, errors: any[] }, warningState, setHints: React.Dispatch<React.SetStateAction<SolHintReport[]>>, hints: SolHintReport[], setSlitherWarnings: React.Dispatch<React.SetStateAction<any[]>>, setSsaWarnings: React.Dispatch<React.SetStateAction<any[]>>) {
if (!isSupportedVersion) return
if (state.data !== null) {
if (lastCompilationResult && (categoryIndex.length > 0 || slitherEnabled)) {
if (lastCompilationResult && (categoryIndex.length > 0 || showSlither)) {
const warningMessage = []
const warningErrors = []
@ -69,6 +69,7 @@ export async function run (lastCompilationResult, lastCompilationSource, current
}
})
})
// iterate over the warnings and create an object
for (const item of result.report) {
let location: any = {}
let locationString = 'not available'
@ -116,16 +117,17 @@ export async function run (lastCompilationResult, lastCompilationSource, current
}
warningErrors.push(options)
warningMessage.push({ msg, options, hasWarning: true, warningModuleName: moduleName })
setSsaWarnings(warningErrors)
}
}
// Slither Analysis
if (slitherEnabled) {
if (showSlither) {
try {
const compilerState = await props.analysisModule.call('solidity', 'getCompilerState')
const { currentVersion, optimize, evmVersion } = compilerState
await props.analysisModule.call('terminal', 'log', { type: 'log', value: '[Slither Analysis]: Running...' })
_paq.push(['trackEvent', 'solidityStaticAnalyzer', 'analyze', 'slitherAnalyzer'])
const result = await props.analysisModule.call('slither', 'analyse', state.file, { currentVersion, optimize, evmVersion })
const result: SlitherAnalysisResults = await props.analysisModule.call('slither', 'analyse', state.file, { currentVersion, optimize, evmVersion })
if (result.status) {
props.analysisModule.call('terminal', 'log', { type: 'log', value: `[Slither Analysis]: Analysis Completed!! ${result.count} warnings found.` })
const report = result.data
@ -138,11 +140,11 @@ export async function run (lastCompilationResult, lastCompilationSource, current
let isLibrary = false
if (item.sourceMap && item.sourceMap.length) {
let path = item.sourceMap[0].source_mapping.filename_relative
const 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)
const getpath = await props.analysisModule.call('fileManager', 'getUrlFromPath', path)
fileIndex = Object.keys(lastCompilationResult.sources).indexOf(getpath.file)
}
if (fileIndex >= 0) {
location = {
@ -165,9 +167,9 @@ export async function run (lastCompilationResult, lastCompilationSource, current
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 msg = message(item.title, item.description, item.more ?? '', fileName, locationString)
const options = {
type: 'warning',
type: item.sourceMap[0].type,
useSpan: true,
errFile: fileName,
fileName,
@ -177,11 +179,16 @@ export async function run (lastCompilationResult, lastCompilationSource, current
item: { warning: item.description },
name: item.title,
locationString,
more: item.more,
more: item.more ?? '',
location: location
}
warningErrors.push(options)
warningMessage.push({ msg, options, hasWarning: true, warningModuleName: 'Slither Analysis' })
const slitherwarnings = []
setSlitherWarnings((prev) => {
slitherwarnings.push(...prev)
slitherwarnings.push({ msg, options, hasWarning: true, warningModuleName: 'Slither Analysis' })
return slitherwarnings
})
}
showWarnings(warningMessage, 'warningModuleName')
}

@ -31,7 +31,7 @@ export function calculateWarningStateEntries(entries: [string, any][]) {
export function BasicTitle(props: BasicTitleProps) {
return (
<span className="rounded-circle">Basic{props.warningStateEntries.length > 0 && !props.hideWarnings ? <i data-id="StaticAnalysisErrorCount" className="badge badge-info rounded-circle ml-2">{calculateWarningStateEntries(props.warningStateEntries).length}</i>: null}
<span>Basic{props.warningStateEntries.length > 0 && !props.hideWarnings ? <i data-id="StaticAnalysisErrorCount" className="badge badge-info rounded-circle ml-1">{calculateWarningStateEntries(props.warningStateEntries).length}</i>: null}
</span>
)
}

@ -67,20 +67,21 @@ export const RemixUiStaticAnalyser = (props: RemixUiStaticAnalyserProps) => {
return indexOfCategory
}
const [basicEnabled, setBasicEnabled] = useState(true)
const [slitherEnabled, setSlitherEnabled] = useState(false)
const [solhintEnabled, setSolhintEnabled] = useState(true) // assuming that solhint is always enabled
const [showSlither, setShowSlither] = useState(false)
const [isSupportedVersion, setIsSupportedVersion] = useState(false)
let [showLibsWarning, setShowLibsWarning] = useState(false) // eslint-disable-line prefer-const
const [categoryIndex, setCategoryIndex] = useState(groupedModuleIndex(groupedModules))
const [warningState, setWarningState] = useState({})
const [runButtonTitle, setRunButtonTitle] = useState<string>('Run Static Analysis')
const [hideWarnings, setHideWarnings] = useState(false)
const [hints, setHints] = useState<SolHintReport[]>([])
const [slitherWarnings, setSlitherWarnings] = useState([])
const [ssaWarnings, setSsaWarnings] = useState([])
const warningContainer = useRef(null)
const allWarnings = useRef({})
const [state, dispatch] = useReducer(analysisReducer, initialState)
const [runButtonTitle, setRunButtonTitle] = useState<string>(`Analyse`)
/**
* Disable static analysis for contracts whose compiler version is
@ -97,7 +98,7 @@ export const RemixUiStaticAnalyser = (props: RemixUiStaticAnalyserProps) => {
setRunButtonTitle('Select Solidity compiler version greater than 0.4.12.')
} else {
setIsSupportedVersion(true)
setRunButtonTitle('Run static analysis')
setRunButtonTitle('Run Analysis')
}
}
@ -108,10 +109,7 @@ export const RemixUiStaticAnalyser = (props: RemixUiStaticAnalyserProps) => {
useEffect(() => {
setWarningState({})
const runAnalysis = async () => {
await run(state.data, state.source, state.file, state, props, isSupportedVersion, slitherEnabled, categoryIndex, groupedModules, runner,_paq, message, showWarnings, allWarnings, warningContainer,calculateWarningStateEntries, warningState, setHints, hints)
// // Run solhint
// const hintsResult = await props.analysisModule.call('solhint', 'lint', state.file)
// setHints(hintsResult)
await run(state.data, state.source, state.file, state, props, isSupportedVersion, showSlither, categoryIndex, groupedModules, runner,_paq, message, showWarnings, allWarnings, warningContainer,calculateWarningStateEntries, warningState, setHints, hints, setSlitherWarnings, setSsaWarnings)
}
if (basicEnabled) {
if (state.data !== null) {
@ -136,11 +134,12 @@ export const RemixUiStaticAnalyser = (props: RemixUiStaticAnalyserProps) => {
// Reset state
dispatch({ type: '', payload: initialState })
setHints([])
setSlitherWarnings([])
setSsaWarnings([])
// Show 'Enable Slither Analysis' checkbox
if (currentWorkspace && currentWorkspace.isLocalhost === true) setShowSlither(true)
else {
setShowSlither(false)
setSlitherEnabled(false)
}
})
props.analysisModule.on('manager', 'pluginDeactivated', (plugin) => {
@ -149,17 +148,15 @@ export const RemixUiStaticAnalyser = (props: RemixUiStaticAnalyserProps) => {
// Reset warning state
setWarningState([])
setHints([])
setSlitherWarnings([])
setSsaWarnings([])
// Reset badge
props.event.trigger('staticAnaysisWarning', [])
// Reset state
dispatch({ type: '', payload: initialState })
setShowSlither(false)
setSlitherEnabled(false)
}
})
const warningResult = calculateWarningStateEntries(Object.entries(warningState))
props.analysisModule.emit('statusChanged', { key: hints.length+warningResult.length,
title: `${hints.length+warningResult.length} warning${hints.length+warningResult.length === 1 ? '' : 's'}`, type: 'warning'})
// eslint-disable-next-line @typescript-eslint/no-unused-vars
props.analysisModule.on('solidity', 'compilerLoaded', async (version: string, license: string) => {
setDisableForRun(version)
@ -167,7 +164,7 @@ export const RemixUiStaticAnalyser = (props: RemixUiStaticAnalyserProps) => {
return () => { }
}, [props])
const message = (name, warning, more, fileName, locationString) : string => {
const message = (name: string, warning: any, more?: string, fileName?: string, locationString?: string) : string => {
return (`
<span className='d-flex flex-column'>
<span className='h6 font-weight-bold'>${name}</span>
@ -255,12 +252,14 @@ export const RemixUiStaticAnalyser = (props: RemixUiStaticAnalyserProps) => {
}
const handleSlitherEnabled = async () => {
const slitherStatus = await props.analysisModule.call('manager', 'isActive', 'remixd')
if (slitherEnabled && slitherStatus) {
setSlitherEnabled(false)
const checkRemixd = await !props.analysisModule.call('manager', 'isActive', 'remixd')
if (showSlither && checkRemixd) {
setShowSlither(false)
await props.analysisModule.call('manager', 'deactivatePlugin', 'remixd')
await props.analysisModule.call('filePanel', 'setWorkspace', 'default_workspace')
} else {
setSlitherEnabled(true)
await props.analysisModule.call('manager', 'activatePlugin', 'remixd')
await props.analysisModule.call('filePanel', 'setWorkspace', { name: 'localhost', isLocalhost: true }, true)
setShowSlither(true)
}
}
@ -362,92 +361,231 @@ export const RemixUiStaticAnalyser = (props: RemixUiStaticAnalyserProps) => {
}
const hintErrors = hints.filter(hint => hint.type === 'error')
const slitherErrors = slitherWarnings.filter(slitherError => slitherError.options.type === 'error')
const tabKeys = [
{
tabKey: 'linter',
tabKey: "linter",
child: (
<>
{hints.length > 0 &&
<div id='solhintlintingresult' className="mb-5">
<div className="mb-4 pt-2">
<Fragment>
{!hideWarnings ? hints.map((hint, index) => (
<div key={index} className={`${hint.type === 'warning' ?
'alert alert-warning' : 'alert alert-danger'}`}>
<div onClick={async () => {
await props.analysisModule.call('editor', 'discardHighlight')
await props.analysisModule.call('editor', 'highlight', {
end: {
line: hint.line, column: hint.column+1
},
start: {
line: hint.line, column: hint.column
}
}, state.file, '', { focus: true })
}}>
<span className="text-wrap">{hint.formattedMessage}</span>
<span>{hint.type}</span><br />
<span>{`${hint.column}:${hint.line}`}</span>
</div>
</div>)) : hintErrors.map((hint, index) => (<div key={index} className='alert alert-danger'>
<div onClick={async () => {
await props.analysisModule.call('editor', 'discardHighlight')
await props.analysisModule.call('editor', 'highlight', {
end: {
line: hint.line, column: hint.column+1
},
start: {
line: hint.line, column: hint.column
}
}, state.file, '', { focus: true })
}}>
<span className="text-wrap">{hint.formattedMessage}</span>
<span>{hint.type}</span><br />
<span>{`${hint.column}:${hint.line}`}</span>
</div>
</div>))
}
</Fragment>
</div>
</div>
}
{hints.length > 0 ? (
<div id="solhintlintingresult" className="mb-5">
<div className="mb-4 pt-2">
<Fragment>
{!hideWarnings
? hints.map((hint, index) => (
<div
key={index}
className={`${
hint.type === "warning"
? "alert alert-warning"
: "alert alert-danger"
}`}
style={{ cursor: "pointer" }}
>
<div
onClick={async () => {
await props.analysisModule.call(
"editor",
"discardHighlight"
);
await props.analysisModule.call(
"editor",
"highlight",
{
end: {
line: hint.line,
column: hint.column + 1,
},
start: {
line: hint.line,
column: hint.column,
},
},
state.file,
"",
{ focus: true }
);
}}
>
<span className="text-wrap">
{hint.formattedMessage}
</span>
<span>{hint.type}</span>
<br />
<span>{`${hint.column}:${hint.line}`}</span>
</div>
</div>
))
: hintErrors.map((hint, index) => (
<div
key={index}
className="alert alert-danger"
style={{ cursor: "pointer" }}
>
<div
onClick={async () => {
await props.analysisModule.call(
"editor",
"discardHighlight"
);
await props.analysisModule.call(
"editor",
"highlight",
{
end: {
line: hint.line,
column: hint.column + 1,
},
start: {
line: hint.line,
column: hint.column,
},
},
state.file,
"",
{ focus: true }
);
}}
>
<span className="text-wrap">
{hint.formattedMessage}
</span>
<span>{hint.type}</span>
<br />
<span>{`${hint.column}:${hint.line}`}</span>
</div>
</div>
))}
</Fragment>
</div>
</div>
) : <span className="display-6">No Results.</span>}
</>
),
title: <span className="rounded-circle">Linter{hints.length > 0 ? hideWarnings ? <i className="badge badge-danger rounded-circle ml-2">{hintErrors.length}</i> : <i className="badge badge-warning rounded-circle ml-2">{hints.length}</i> : null}</span>
title: (
<span>
Linter
{hints.length > 0 ? (
hideWarnings ? (
<i className="badge badge-info rounded-circle ml-1">
{hintErrors.length}
</i>
) : (
<i className="badge badge-info rounded-circle ml-1">
{hints.length}
</i>
)
) : null}
</span>
),
},
{
tabKey: 'basic',
title: <BasicTitle warningStateEntries={Object.entries(warningState)} hideWarnings={hideWarnings} />,
child: <>
{Object.entries(warningState).length > 0 &&
<div id='staticanalysisresult' >
tabKey: "basic",
title: (
<BasicTitle
warningStateEntries={Object.entries(warningState)}
hideWarnings={hideWarnings}
/>
),
child: (
<>
{Object.entries(warningState).length > 0 ? (
<div id="staticanalysisresult">
<div className="mb-4 pt-2">
{
(Object.entries(warningState).map((element, index) => (
<div key={index}>
{element[1]['map']((x, i) => ( // eslint-disable-line dot-notation
x.hasWarning && !hideWarnings ? ( // eslint-disable-next-line dot-notation
<div data-id={`staticAnalysisModule${x.warningModuleName}${i}`} id={`staticAnalysisModule${x.warningModuleName}${i}`} key={i}>
<ErrorRenderer name={`staticAnalysisModule${x.warningModuleName}${i}`} message={x.msg} opt={x.options} warningErrors={ x.warningErrors} editor={props.analysisModule}/>
{Object.entries(warningState).map((element, index) => (
<div key={index}>
{element[1]["map"](
(x,i) => // eslint-disable-line dot-notation
x.hasWarning && !hideWarnings
? ( // eslint-disable-next-line dot-notation
<div
data-id={`staticAnalysisModule${x.warningModuleName}${i}`}
id={`staticAnalysisModule${x.warningModuleName}${i}`}
key={i}
>
<ErrorRenderer
name={`staticAnalysisModule${x.warningModuleName}${i}`}
message={x.msg}
opt={x.options}
warningErrors={x.warningErrors}
editor={props.analysisModule}
/>
</div>
) : null
))}
{}
</div>
)))
}
)}
{}
</div>
))}
</div>
</div>
}
</>
) : <span className="display-6">No Results.</span>}
</>
),
},
{
tabKey: 'slither',
title: 'Slither',
child: <div></div>,
}
]
tabKey: "slither",
title: (
<span>
Slither
{slitherWarnings.length > 0 ? (
hideWarnings ? (
<i className="badge badge-info rounded-circle ml-1">
{slitherErrors.length}
</i>
) : (
<i className="badge badge-info rounded-circle ml-1">
{slitherWarnings.length}
</i>
)
) : null}
</span>
),
child: (
<>
{slitherWarnings.length > 0 ? (
<div id="solhintlintingresult" className="mb-5">
<div className="mb-4 pt-2">
<Fragment>
{!hideWarnings
? slitherWarnings.map((warning, index) => (
<div
data-id={`staticAnalysisModule${warning.warningModuleName}${index}`}
id={`staticAnalysisModule${warning.warningModuleName}${index}`}
key={index}
>
<ErrorRenderer
name={`staticAnalysisModule${warning.warningModuleName}${index}`}
message={warning.msg}
opt={warning.options}
warningErrors={warning.warningErrors}
editor={props.analysisModule}
/>
</div>
))
: slitherWarnings.filter(x => x.options.type === 'error').map((warning, index) => (
<div
data-id={`staticAnalysisModule${warning.warningModuleName}${index}`}
id={`staticAnalysisModule${warning.warningModuleName}${index}`}
key={index}
>
<ErrorRenderer
name={`staticAnalysisModule${warning.warningModuleName}${index}`}
message={warning.msg}
opt={warning.options}
warningErrors={warning.warningErrors}
editor={props.analysisModule}
/>
</div>
))}
</Fragment>
</div>
</div>
) : <span className="display-6">No Result</span>}
</>
),
},
];
const t = Object.entries(warningState)
const checkBasicStatus = () => {
return Object.values(groupedModules).map((value: any) => {
@ -492,18 +630,18 @@ export const RemixUiStaticAnalyser = (props: RemixUiStaticAnalyserProps) => {
id="enableSlither"
inputType="checkbox"
onClick={handleSlitherEnabled}
checked={slitherEnabled}
checked={showSlither}
label="Slither"
onChange={() => {}}
/>
</div>
<Button
buttonText="Analyse"
title={runButtonTitle}
buttonText={`Analyse ${state.file}`}
title={`${runButtonTitle}`}
classList="btn btn-sm btn-primary btn-block"
onClick={async () => await run(state.data, state.source, state.file, state , props, isSupportedVersion, slitherEnabled, categoryIndex, groupedModules, runner,_paq,
message, showWarnings, allWarnings, warningContainer, calculateWarningStateEntries, warningState, setHints, hints)}
disabled={(state.data === null || categoryIndex.length === 0) && !slitherEnabled || !isSupportedVersion }
onClick={async () => await run(state.data, state.source, state.file, state , props, isSupportedVersion, showSlither, categoryIndex, groupedModules, runner,_paq,
message, showWarnings, allWarnings, warningContainer, calculateWarningStateEntries, warningState, setHints, hints, setSlitherWarnings, setSsaWarnings)}
disabled={(state.data === null || !isSupportedVersion) || (!solhintEnabled && !basicEnabled) }
/>
<div className="mt-4 p-2 d-flex border-top flex-column">
<span>Last results for:</span>
@ -519,17 +657,18 @@ export const RemixUiStaticAnalyser = (props: RemixUiStaticAnalyserProps) => {
id="showLibWarnings"
name="showLibWarnings"
categoryId="showLibWarnings"
title="when checked, the results are also displayed for external contract libraries"
title="When checked, the results are also displayed for external contract libraries."
inputType="checkbox"
checked={showLibsWarning}
label="Show warnings for external libraries"
onClick={handleShowLibsWarning}
onChange={() => {}}
tooltipPlacement="top-start"
/>
<RemixUiCheckbox
id="hideWarnings"
name="hideWarnings"
title="when checked, general warnings from analysis are hidden"
title="When checked, general warnings from analysis are hidden."
inputType="checkbox"
checked={hideWarnings}
label="Hide warnings"

@ -83,3 +83,56 @@ type ErrorRendererOptions = {
}
}
type SlitherAnalysisResultType = {
description: string,
title: string,
confidence: string,
severity: string,
more?: any,
sourceMap: [
{
type: string,
name: string,
source_mapping: {
start: number,
length: number,
filename_relative: string,
filename_absolute: string,
filename_short: string,
is_dependency: false,
lines: number[],
starting_column: number,
ending_column: number
},
type_specific_fields: {
parent: {
type: string,
name: string,
source_mapping: {
start: number,
length: number,
filename_relative: string,
filename_absolute: string,
filename_short: string,
is_dependency: false,
lines: number[],
starting_column: number,
ending_column: number
}
},
signature: string
},
additional_fields: {
target: string,
convention: string
}
}
]
}
export type SlitherAnalysisResults = {
count: number,
data: SlitherAnalysisResultType[]
status: boolean
}

@ -52,13 +52,13 @@ function Badge ({ badgeStatus }: BadgeProps) {
placement={'right'}
tooltipClasses="text-nowrap"
tooltipId="verticalItemsbadge"
tooltipText={badgeStatus.pluginName && badgeStatus.pluginName === 'solidityStaticAnalysis' ? 'There are multiple warnings or errors that might need to be fixed' :badgeStatus.title}
tooltipText={badgeStatus.pluginName && badgeStatus.pluginName === 'solidityStaticAnalysis' ? 'There are multiple warnings or errors that might need to be fixed.' :badgeStatus.title}
>
<i
className={resolveClasses(badgeStatus.key, badgeStatus.type!)}
className={`${resolveClasses(badgeStatus.key, badgeStatus.type!)}`}
aria-hidden="true"
>
{ badgeStatus.pluginName && badgeStatus.pluginName === 'solidityStaticAnalysis' ? '.' : badgeStatus.text}
{ badgeStatus.pluginName && badgeStatus.pluginName === 'solidityStaticAnalysis' ? <span>&nbsp;</span> : badgeStatus.text }
</i>
</CustomTooltip>
) : null

Loading…
Cancel
Save