import { LibSdbTypes } from "./types/types" ; import { LibSdbUtils } from "./utils/utils" ; import { LibSdbRuntime } from "./runtime" ; export class LibSdbBreakpoints { private _runtime : LibSdbRuntime ; private _breakpointId : number ; constructor ( ) { this . _runtime = LibSdbRuntime . instance ( ) ; this . _breakpointId = 1 ; } public async setBreakpoint ( path , line , visible = true , originalSource = true ) { if ( ! this . _runtime . _files . has ( path ) ) { this . _runtime . _files . set ( path , new LibSdbTypes . File ( "/" , path ) ) ; } const file = this . _runtime . _files . get ( path ) ! ; const originalLine = line ; if ( originalSource ) { line = LibSdbUtils . getNewLine ( line , file . lineOffsets ) ; } let bp = new LibSdbTypes . Breakpoint ( ) ; bp . verified = false ; bp . line = line ; bp . id = this . _breakpointId ++ ; bp . visible = visible ; bp . originalSource = originalSource ; if ( file ) { if ( file . breakpoints . indexOf ( bp ) === - 1 ) { file . breakpoints . push ( bp ) ; } await this . verifyBreakpoints ( path ) ; } let bpForUI = bp . clone ( ) ; bpForUI . line = originalLine ; return bpForUI ; } public async verifyAllBreakpoints ( ) { for ( const file of this . _runtime . _files ) { await this . verifyBreakpoints ( file [ 0 ] ) ; } } private getSmallestPC ( sourceLocations , bytecode ) { let pc = null ; for ( let k = 0 ; k < sourceLocations . length ; k ++ ) { const sourceLocation = sourceLocations [ k ] ; const index = LibSdbUtils . SourceMappingDecoder . toIndex ( sourceLocation , bytecode . srcMap ) ; if ( index !== null ) { for ( const entry of bytecode . pcMap . entries ( ) ) { if ( entry [ 1 ] . index === index ) { if ( pc === null || entry [ 0 ] < pc ) { pc = entry [ 0 ] ; } break ; } } } } return pc ; } private async createBreakpoints ( bp , startPosition , endPosition , pc , file , contract , bpIsRuntime ) { if ( bpIsRuntime ) { contract . runtimeBytecode . breakpoints . set ( bp . id , pc ) ; } else { contract . creationBytecode . breakpoints . set ( bp . id , pc ) ; } for ( let k = 0 ; k < contract . addresses . length ; k ++ ) { await this . _runtime . _interface . requestSendBreakpoint ( bp . id , contract . addresses [ k ] , pc , true , bpIsRuntime ) ; } for ( let k = 0 ; k < file . contracts . length ; k ++ ) { const childContract = file . contracts [ k ] ; if ( childContract . inheritedContracts . indexOf ( contract ) >= 0 ) { let childContractSourceMap ; if ( bpIsRuntime ) { childContractSourceMap = LibSdbUtils . SourceMappingDecoder . decompressAll ( childContract . runtimeBytecode . srcMap ) ; } else { childContractSourceMap = LibSdbUtils . SourceMappingDecoder . decompressAll ( childContract . creationBytecode . srcMap ) ; } for ( let l = 0 ; l < childContractSourceMap . length ; l ++ ) { let childIndex = null ; let childPc = null ; const sourceLocation = childContractSourceMap [ l ] ; if ( ! ( startPosition <= sourceLocation . start && sourceLocation . start <= endPosition ) ) { continue ; } if ( bpIsRuntime ) { childIndex = LibSdbUtils . SourceMappingDecoder . toIndex ( sourceLocation , childContract . runtimeBytecode . srcMap ) ; } else { childIndex = LibSdbUtils . SourceMappingDecoder . toIndex ( sourceLocation , childContract . creationBytecode . srcMap ) ; } if ( childIndex !== null ) { const pcMap = bpIsRuntime ? childContract . runtimeBytecode . pcMap : childContract . creationBytecode . pcMap ; for ( const entry of pcMap . entries ( ) ) { if ( entry [ 1 ] . index === childIndex ) { if ( childPc === null || entry [ 0 ] < childPc ) { childPc = entry [ 0 ] ; } break ; } } } if ( childPc !== null ) { if ( bpIsRuntime ) { childContract . runtimeBytecode . breakpoints . set ( bp . id , pc ) ; } else { childContract . creationBytecode . breakpoints . set ( bp . id , pc ) ; } for ( let m = 0 ; m < childContract . addresses . length ; m ++ ) { await this . _runtime . _interface . requestSendBreakpoint ( bp . id , childContract . addresses [ m ] , childPc , true , bpIsRuntime ) ; } break ; } } } } } public async verifyBreakpoints ( path ) { const file = this . _runtime . _files . get ( path ) ; if ( file ) { for ( let i = 0 ; i < file . breakpoints . length ; i ++ ) { const bp = file . breakpoints [ i ] ; bp . verified = true ; this . _runtime . sendEvent ( 'breakpointValidated' , bp ) ; const astWalker = new LibSdbUtils . AstWalker ( ) ; const startPosition = bp . line === 0 ? 0 : file . lineBreaks [ bp . line - 1 ] + 1 ; const endPosition = file . lineBreaks [ bp . line ] ; for ( let j = 0 ; j < file . contracts . length ; j ++ ) { let sourceLocations = [ ] ; const contract = file . contracts [ j ] ; astWalker . walk ( contract . ast , ( node ) => { if ( node . src ) { const srcSplit = node . src . split ( ":" ) ; const pos = parseInt ( srcSplit [ 0 ] ) ; if ( startPosition <= pos && pos <= endPosition && node . name !== "VariableDeclarationStatement" && node . name !== "VariableDeclaration" ) { sourceLocations . push ( { start : parseInt ( srcSplit [ 0 ] ) , length : parseInt ( srcSplit [ 1 ] ) , file : parseInt ( srcSplit [ 2 ] ) } ) ; } } return true ; } ) ; const creationPC = this . getSmallestPC ( sourceLocations , contract . creationBytecode ) ; const runtimePC = this . getSmallestPC ( sourceLocations , contract . runtimeBytecode ) ; if ( creationPC !== null ) { await this . createBreakpoints ( bp , startPosition , endPosition , creationPC , file , contract , false ) ; } if ( runtimePC !== null ) { await this . createBreakpoints ( bp , startPosition , endPosition , runtimePC , file , contract , true ) ; } if ( creationPC !== null || runtimePC !== null ) { break ; } } ; } ; } } public async clearBreakpoint ( path , line ) { const file = this . _runtime . _files . get ( path ) ; if ( file ) { const index = file . breakpoints . findIndex ( bp => bp . line === line ) ; if ( index >= 0 ) { const bp = file . breakpoints [ index ] ; for ( let i = 0 ; i < file . contracts . length ; i ++ ) { file . contracts [ i ] . creationBytecode . breakpoints . delete ( bp . id ) ; file . contracts [ i ] . runtimeBytecode . breakpoints . delete ( bp . id ) ; } await this . _runtime . _interface . requestSendBreakpoint ( bp . id , "" , 0 , false , true ) ; file . breakpoints . splice ( index , 1 ) ; return bp ; } } return undefined ; } public async clearBreakpoints ( path ) { const file = this . _runtime . _files . get ( path ) ; if ( file ) { for ( let i = 0 ; i < file . breakpoints . length ; i ++ ) { for ( let j = 0 ; j < file . contracts . length ; j ++ ) { file . contracts [ j ] . creationBytecode . breakpoints . delete ( file . breakpoints [ i ] . id ) ; file . contracts [ j ] . runtimeBytecode . breakpoints . delete ( file . breakpoints [ i ] . id ) ; } await this . _runtime . _interface . requestSendBreakpoint ( file . breakpoints [ i ] . id , "" , 0 , false , true ) ; } file . breakpoints = [ ] ; } } }