1// Copyright 2016 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5// This file implements the compressed encoding of source
6// positions using a lookup table.
7
8package src
9
10// XPos is a more compact representation of Pos.
11type XPos struct {
12	index int32
13	lico
14}
15
16// NoXPos is a valid unknown position.
17var NoXPos XPos
18
19// IsKnown reports whether the position p is known.
20// XPos.IsKnown() matches Pos.IsKnown() for corresponding
21// positions.
22func (p XPos) IsKnown() bool {
23	return p.index != 0 || p.Line() != 0
24}
25
26// Before reports whether the position p comes before q in the source.
27// For positions with different bases, ordering is by base index.
28func (p XPos) Before(q XPos) bool {
29	n, m := p.index, q.index
30	return n < m || n == m && p.lico < q.lico
31}
32
33// SameFile reports whether p and q are positions in the same file.
34func (p XPos) SameFile(q XPos) bool {
35	return p.index == q.index
36}
37
38// SameFileAndLine reports whether p and q are positions on the same line in the same file.
39func (p XPos) SameFileAndLine(q XPos) bool {
40	return p.index == q.index && p.lico.SameLine(q.lico)
41}
42
43// After reports whether the position p comes after q in the source.
44// For positions with different bases, ordering is by base index.
45func (p XPos) After(q XPos) bool {
46	n, m := p.index, q.index
47	return n > m || n == m && p.lico > q.lico
48}
49
50// WithNotStmt returns the same location to be marked with DWARF is_stmt=0
51func (p XPos) WithNotStmt() XPos {
52	p.lico = p.lico.withNotStmt()
53	return p
54}
55
56// WithDefaultStmt returns the same location with undetermined is_stmt
57func (p XPos) WithDefaultStmt() XPos {
58	p.lico = p.lico.withDefaultStmt()
59	return p
60}
61
62// WithIsStmt returns the same location to be marked with DWARF is_stmt=1
63func (p XPos) WithIsStmt() XPos {
64	p.lico = p.lico.withIsStmt()
65	return p
66}
67
68// WithBogusLine returns a bogus line that won't match any recorded for the source code.
69// Its use is to disrupt the statements within an infinite loop so that the debugger
70// will not itself loop infinitely waiting for the line number to change.
71// gdb chooses not to display the bogus line; delve shows it with a complaint, but the
72// alternative behavior is to hang.
73func (p XPos) WithBogusLine() XPos {
74	if p.index == 0 {
75		// See #35652
76		panic("Assigning a bogus line to XPos with no file will cause mysterious downstream failures.")
77	}
78	p.lico = makeBogusLico()
79	return p
80}
81
82// WithXlogue returns the same location but marked with DWARF function prologue/epilogue
83func (p XPos) WithXlogue(x PosXlogue) XPos {
84	p.lico = p.lico.withXlogue(x)
85	return p
86}
87
88// LineNumber returns a string for the line number, "?" if it is not known.
89func (p XPos) LineNumber() string {
90	if !p.IsKnown() {
91		return "?"
92	}
93	return p.lico.lineNumber()
94}
95
96// FileIndex returns a smallish non-negative integer corresponding to the
97// file for this source position.  Smallish is relative; it can be thousands
98// large, but not millions.
99func (p XPos) FileIndex() int32 {
100	return p.index
101}
102
103func (p XPos) LineNumberHTML() string {
104	if !p.IsKnown() {
105		return "?"
106	}
107	return p.lico.lineNumberHTML()
108}
109
110// AtColumn1 returns the same location but shifted to column 1.
111func (p XPos) AtColumn1() XPos {
112	p.lico = p.lico.atColumn1()
113	return p
114}
115
116// A PosTable tracks Pos -> XPos conversions and vice versa.
117// Its zero value is a ready-to-use PosTable.
118type PosTable struct {
119	baseList []*PosBase
120	indexMap map[*PosBase]int
121	nameMap  map[string]int // Maps file symbol name to index for debug information.
122}
123
124// XPos returns the corresponding XPos for the given pos,
125// adding pos to t if necessary.
126func (t *PosTable) XPos(pos Pos) XPos {
127	return XPos{t.baseIndex(pos.base), pos.lico}
128}
129
130func (t *PosTable) baseIndex(base *PosBase) int32 {
131	if base == nil {
132		return 0
133	}
134
135	if i, ok := t.indexMap[base]; ok {
136		return int32(i)
137	}
138
139	if base.fileIndex >= 0 {
140		panic("PosBase already registered with a PosTable")
141	}
142
143	if t.indexMap == nil {
144		t.baseList = append(t.baseList, nil)
145		t.indexMap = make(map[*PosBase]int)
146		t.nameMap = make(map[string]int)
147	}
148
149	i := len(t.baseList)
150	t.indexMap[base] = i
151	t.baseList = append(t.baseList, base)
152
153	fileIndex, ok := t.nameMap[base.absFilename]
154	if !ok {
155		fileIndex = len(t.nameMap)
156		t.nameMap[base.absFilename] = fileIndex
157	}
158	base.fileIndex = fileIndex
159
160	return int32(i)
161}
162
163// Pos returns the corresponding Pos for the given p.
164// If p cannot be translated via t, the function panics.
165func (t *PosTable) Pos(p XPos) Pos {
166	var base *PosBase
167	if p.index != 0 {
168		base = t.baseList[p.index]
169	}
170	return Pos{base, p.lico}
171}
172
173// FileTable returns a slice of all files used to build this package.
174func (t *PosTable) FileTable() []string {
175	// Create a LUT of the global package level file indices. This table is what
176	// is written in the debug_lines header, the file[N] will be referenced as
177	// N+1 in the debug_lines table.
178	fileLUT := make([]string, len(t.nameMap))
179	for str, i := range t.nameMap {
180		fileLUT[i] = str
181	}
182	return fileLUT
183}
184