package wasmdebug

import (
	
	
	
	
	
	
	
)

// DWARFLines is used to retrieve source code line information from the DWARF data.
type DWARFLines struct {
	// d is created by DWARF custom sections.
	d *dwarf.Data
	// linesPerEntry maps dwarf.Offset for dwarf.Entry to the list of lines contained by the entry.
	// The value is sorted in the increasing order by the address.
	linesPerEntry map[dwarf.Offset][]line
	mux           sync.Mutex
}

type line struct {
	addr uint64
	pos  dwarf.LineReaderPos
}

// NewDWARFLines returns DWARFLines for the given *dwarf.Data.
func ( *dwarf.Data) *DWARFLines {
	if  == nil {
		return nil
	}
	return &DWARFLines{d: , linesPerEntry: map[dwarf.Offset][]line{}}
}

// isTombstoneAddr returns true if the given address is invalid a.k.a tombstone address which was made no longer valid
// by linker. According to the DWARF spec[1], the value is encoded as 0xffffffff for Wasm (as 32-bit target),
// but some tools encode it either in -1, -2 [2] or 1<<32 (This might not be by tools, but by debug/dwarf package's bug).
//
// [1] https://dwarfstd.org/issues/200609.1.html
// [2] https://github.com/WebAssembly/binaryen/blob/97178d08d4a20d2a5e3a6be813fc6a7079ef86e1/src/wasm/wasm-debug.cpp#L651-L660
// [3] https://reviews.llvm.org/D81784
func isTombstoneAddr( uint64) bool {
	 := int32()
	return  == -1 ||  == -2 ||
		 == 0 // This covers 1 <<32.
}

// Line returns the line information for the given instructionOffset which is an offset in
// the code section of the original Wasm binary. Returns empty string if the info is not found.
func ( *DWARFLines) ( uint64) ( []string) {
	if  == nil {
		return
	}

	// DWARFLines is created per Wasm binary, so there's a possibility that multiple instances
	// created from a same binary face runtime error at the same time, and that results in
	// concurrent access to this function.
	.mux.Lock()
	defer .mux.Unlock()

	 := .d.Reader()

	var  []*dwarf.Entry
	var  *dwarf.Entry
	var  bool
:
	for {
		,  := .Next()
		if  != nil ||  == nil {
			break
		}

		// If we already found the compilation unit and relevant inlined routines, we can stop searching entries.
		if  != nil &&  {
			break
		}

		switch .Tag {
		case dwarf.TagCompileUnit, dwarf.TagInlinedSubroutine:
		default:
			// Only CompileUnit and InlinedSubroutines are relevant.
			continue
		}

		// Check if the entry spans the range which contains the target instruction.
		,  := .d.Ranges()
		if  != nil {
			continue
		}
		for ,  := range  {
			,  := [0], [1]
			if isTombstoneAddr() || isTombstoneAddr() {
				continue
			}
			if  <=  &&  <  {
				switch .Tag {
				case dwarf.TagCompileUnit:
					 = 
				case dwarf.TagInlinedSubroutine:
					 = append(, )
					// Search inlined subroutines until all the children.
					 = !.Children
					// Not that "children" in the DWARF spec is defined as the next entry to this entry.
					// See "2.3 Relationship of Debugging Information Entries" in https://dwarfstd.org/doc/DWARF4.pdf
				}
				continue 
			}
		}
	}

	// If the relevant compilation unit is not found, nothing we can do with this DWARF info.
	if  == nil {
		return
	}

	,  := .d.LineReader()
	if  != nil ||  == nil {
		return
	}
	var  []line
	var  bool
	var  dwarf.LineEntry
	// Get the lines inside the entry.
	if ,  = .linesPerEntry[.Offset]; ! {
		// If not found, we create the list of lines by reading all the LineEntries in the Entry.
		//
		// Note that the dwarf.LineEntry.SeekPC API shouldn't be used because the Go's dwarf package assumes that
		// all the line entries in an Entry are sorted in increasing order which *might not* be true
		// for some languages. Such order requirement is not a part of DWARF specification,
		// and in fact Zig language tends to emit interleaved line information.
		//
		// Thus, here we read all line entries here, and sort them in the increasing order wrt addresses.
		for {
			 := .Tell()
			 = .Next(&)
			if errors.Is(, io.EOF) {
				break
			} else if  != nil {
				return
			}
			// TODO: Maybe we should ignore tombstone addresses by using isTombstoneAddr,
			//  but not sure if that would be an issue in practice.
			 = append(, line{addr: .Address, pos: })
		}
		sort.Slice(, func(,  int) bool { return [].addr < [].addr })
		.linesPerEntry[.Offset] =  // Caches for the future inquiries for the same Entry.
	}

	// Now we have the lines for this entry. We can find the corresponding source line for instructionOffset
	// via binary search on the list.
	 := len()
	 := sort.Search(, func( int) bool { return [].addr >=  })

	if  ==  { // This case the address is not found. See the doc sort.Search.
		return
	}

	 := []
	if .addr !=  {
		// If the address doesn't match exactly, the previous entry is the one that contains the instruction.
		// That can happen anytime as the DWARF spec allows it, and other tools can handle it in this way conventionally
		// https://github.com/gimli-rs/addr2line/blob/3a2dbaf84551a06a429f26e9c96071bb409b371f/src/lib.rs#L236-L242
		// https://github.com/kateinoigakukun/wasminspect/blob/f29f052f1b03104da9f702508ac0c1bbc3530ae4/crates/debugger/src/dwarf/mod.rs#L453-L459
		if -1 < 0 {
			return
		}
		 = [-1]
	}

	// Advance the line reader for the found position.
	.Seek(.pos)
	 = .Next(&)
	if  != nil {
		// If we reach this block, that means there's a bug in the []line creation logic above.
		panic("BUG: stored dwarf.LineReaderPos is invalid")
	}

	// In the inlined case, the line info is the innermost inlined function call.
	 := len() != 0
	 := fmt.Sprintf("%#x: ", )
	 = append(, formatLine(, .File.Name, int64(.Line), int64(.Column), ))

	if  {
		 = strings.Repeat(" ", len())
		 := .Files()
		// inlinedRoutines contain the inlined call information in the reverse order (children is higher than parent),
		// so we traverse the reverse order and emit the inlined calls.
		for  := len() - 1;  >= 0; -- {
			 := []
			,  := .Val(dwarf.AttrCallFile).(int64)
			if ! {
				return
			} else if  >= int64(len()) {
				// This in theory shouldn't happen according to the spec, but guard against ill-formed DWARF info.
				return
			}
			 := []
			,  := .Val(dwarf.AttrCallLine).(int64)
			,  := .Val(dwarf.AttrCallColumn).(int64)
			 = append(, formatLine(, .Name, , ,
				// Last one is the origin of the inlined function calls.
				 != 0))
		}
	}
	return
}

func formatLine(,  string, ,  int64,  bool) string {
	 := strings.Builder{}
	.WriteString()
	.WriteString()

	if  != 0 {
		.WriteString(fmt.Sprintf(":%d", ))
		if  != 0 {
			.WriteString(fmt.Sprintf(":%d", ))
		}
	}

	if  {
		.WriteString(" (inlined)")
	}
	return .String()
}