// Copyright Fuzamei Corp. 2018 All Rights Reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package runtime

import (
	"github.com/33cn/plugin/plugin/dapp/evm/executor/vm/common"
	"github.com/holiman/uint256"
)

// Destinations 存储合约以及代码对应的位向量对象
// Key为代码哈希，Value为对应的合约代码的位向量
// JUMPDEST指令会使用此对象进行跳转位置判断
type Destinations map[common.Hash]bitvec

// Has 检查PC只想的代码是否存在JUMPDEST指令，并且跳转目标有效
func (d Destinations) Has(codehash common.Hash, code []byte, dest *uint256.Int) bool {
	// 首先需要检查PC（指令指针），它不可能比代码长度还大，也不可能大于63位
	// 注意，这里的参数dest就是PC指针
	udest, overflow := dest.Uint64WithOverflow()
	// PC cannot go beyond len(code) and certainly can't be bigger than 63bits.
	// Don't bother checking for JUMPDEST in that case.
	if overflow || udest >= uint64(len(code)) {
		return false
	}

	// 查看丢应代码是否已经解析，如果没有则，立即执行解析
	m, analysed := d[codehash]
	if !analysed {
		m = codeBitmap(code)
		d[codehash] = m
	}

	// PC跳转位置，并且跳转的目的地为有效代码段
	return OpCode(code[udest]) == JUMPDEST && m.codeSegment(udest)
}

// 位向量，未设置的位表示操作码，设置的位表示数据
type bitvec []byte

func (bits *bitvec) set(pos uint64) {
	(*bits)[pos/8] |= 0x80 >> (pos % 8)
}
func (bits *bitvec) set8(pos uint64) {
	(*bits)[pos/8] |= 0xFF >> (pos % 8)
	(*bits)[pos/8+1] |= ^(0xFF >> (pos % 8))
}

// 检查pos对应的位置是否为有效代码段开始
func (bits *bitvec) codeSegment(pos uint64) bool {
	return ((*bits)[pos/8] & (0x80 >> (pos % 8))) == 0
}

// 检查区分代码中的指令和数据，并将数据位置1
func codeBitmap(code []byte) bitvec {
	// bitmap长度会多加上4，避免代码以push32结束（这种情况下需要在最后设置4个byte，也就是32个位）
	//
	bits := make(bitvec, len(code)/8+1+4)
	for pc := uint64(0); pc < uint64(len(code)); {
		op := OpCode(code[pc])

		// 从PUSH1到PUSH32的指令是连续的数字，下面才能这样处理
		if op >= PUSH1 && op <= PUSH32 {
			numbits := op - PUSH1 + 1
			pc++

			// 最小按照每8位进行设置
			for ; numbits >= 8; numbits -= 8 {
				bits.set8(pc) // 8
				pc += 8
			}

			// 然后再设置剩余的不足8位的bit
			for ; numbits > 0; numbits-- {
				bits.set(pc)
				pc++
			}
		} else {
			pc++
		}
	}
	return bits
}
