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 encoding of source positions.
6
7package src
8
9import (
10	"bytes"
11	"fmt"
12	"io"
13)
14
15// A Pos encodes a source position consisting of a (line, column) number pair
16// and a position base. A zero Pos is a ready to use "unknown" position (nil
17// position base and zero line number).
18//
19// The (line, column) values refer to a position in a file independent of any
20// position base ("absolute" file position).
21//
22// The position base is used to determine the "relative" position, that is the
23// filename and line number relative to the position base. If the base refers
24// to the current file, there is no difference between absolute and relative
25// positions. If it refers to a //line directive, a relative position is relative
26// to that directive. A position base in turn contains the position at which it
27// was introduced in the current file.
28type Pos struct {
29	base *PosBase
30	lico
31}
32
33// NoPos is a valid unknown position.
34var NoPos Pos
35
36// MakePos creates a new Pos value with the given base, and (file-absolute)
37// line and column.
38func MakePos(base *PosBase, line, col uint) Pos {
39	return Pos{base, makeLico(line, col)}
40}
41
42// IsKnown reports whether the position p is known.
43// A position is known if it either has a non-nil
44// position base, or a non-zero line number.
45func (p Pos) IsKnown() bool {
46	return p.base != nil || p.Line() != 0
47}
48
49// Before reports whether the position p comes before q in the source.
50// For positions in different files, ordering is by filename.
51func (p Pos) Before(q Pos) bool {
52	n, m := p.Filename(), q.Filename()
53	return n < m || n == m && p.lico < q.lico
54}
55
56// After reports whether the position p comes after q in the source.
57// For positions in different files, ordering is by filename.
58func (p Pos) After(q Pos) bool {
59	n, m := p.Filename(), q.Filename()
60	return n > m || n == m && p.lico > q.lico
61}
62
63func (p Pos) LineNumber() string {
64	if !p.IsKnown() {
65		return "?"
66	}
67	return p.lico.lineNumber()
68}
69
70func (p Pos) LineNumberHTML() string {
71	if !p.IsKnown() {
72		return "?"
73	}
74	return p.lico.lineNumberHTML()
75}
76
77// Filename returns the name of the actual file containing this position.
78func (p Pos) Filename() string { return p.base.Pos().RelFilename() }
79
80// Base returns the position base.
81func (p Pos) Base() *PosBase { return p.base }
82
83// SetBase sets the position base.
84func (p *Pos) SetBase(base *PosBase) { p.base = base }
85
86// RelFilename returns the filename recorded with the position's base.
87func (p Pos) RelFilename() string { return p.base.Filename() }
88
89// RelLine returns the line number relative to the position's base.
90func (p Pos) RelLine() uint {
91	b := p.base
92	if b.Line() == 0 {
93		// base line is unknown => relative line is unknown
94		return 0
95	}
96	return b.Line() + (p.Line() - b.Pos().Line())
97}
98
99// RelCol returns the column number relative to the position's base.
100func (p Pos) RelCol() uint {
101	b := p.base
102	if b.Col() == 0 {
103		// base column is unknown => relative column is unknown
104		// (the current specification for line directives requires
105		// this to apply until the next PosBase/line directive,
106		// not just until the new newline)
107		return 0
108	}
109	if p.Line() == b.Pos().Line() {
110		// p on same line as p's base => column is relative to p's base
111		return b.Col() + (p.Col() - b.Pos().Col())
112	}
113	return p.Col()
114}
115
116// AbsFilename() returns the absolute filename recorded with the position's base.
117func (p Pos) AbsFilename() string { return p.base.AbsFilename() }
118
119// FileIndex returns the file index of the position's base's absolute
120// filename within the PosTable that it was registered.
121func (p Pos) FileIndex() int { return p.base.FileIndex() }
122
123func (p Pos) String() string {
124	return p.Format(true, true)
125}
126
127// Format formats a position as "filename:line" or "filename:line:column",
128// controlled by the showCol flag and if the column is known (!= 0).
129// For positions relative to line directives, the original position is
130// shown as well, as in "filename:line[origfile:origline:origcolumn] if
131// showOrig is set.
132func (p Pos) Format(showCol, showOrig bool) string {
133	buf := new(bytes.Buffer)
134	p.WriteTo(buf, showCol, showOrig)
135	return buf.String()
136}
137
138// WriteTo a position to w, formatted as Format does.
139func (p Pos) WriteTo(w io.Writer, showCol, showOrig bool) {
140	if !p.IsKnown() {
141		io.WriteString(w, "<unknown line number>")
142		return
143	}
144
145	if b := p.base; b == b.Pos().base {
146		// base is file base (incl. nil)
147		format(w, p.Filename(), p.Line(), p.Col(), showCol)
148		return
149	}
150
151	// base is relative
152	// Print the column only for the original position since the
153	// relative position's column information may be bogus (it's
154	// typically generated code and we can't say much about the
155	// original source at that point but for the file:line info
156	// that's provided via a line directive).
157	// TODO(gri) This may not be true if we have an inlining base.
158	// We may want to differentiate at some point.
159	format(w, p.RelFilename(), p.RelLine(), p.RelCol(), showCol)
160	if showOrig {
161		io.WriteString(w, "[")
162		format(w, p.Filename(), p.Line(), p.Col(), showCol)
163		io.WriteString(w, "]")
164	}
165}
166
167// format formats a (filename, line, col) tuple as "filename:line" (showCol
168// is false or col == 0) or "filename:line:column" (showCol is true and col != 0).
169func format(w io.Writer, filename string, line, col uint, showCol bool) {
170	io.WriteString(w, filename)
171	io.WriteString(w, ":")
172	fmt.Fprint(w, line)
173	// col == 0 and col == colMax are interpreted as unknown column values
174	if showCol && 0 < col && col < colMax {
175		io.WriteString(w, ":")
176		fmt.Fprint(w, col)
177	}
178}
179
180// formatstr wraps format to return a string.
181func formatstr(filename string, line, col uint, showCol bool) string {
182	buf := new(bytes.Buffer)
183	format(buf, filename, line, col, showCol)
184	return buf.String()
185}
186
187// ----------------------------------------------------------------------------
188// PosBase
189
190// A PosBase encodes a filename and base position.
191// Typically, each file and line directive introduce a PosBase.
192type PosBase struct {
193	pos         Pos    // position at which the relative position is (line, col)
194	filename    string // file name used to open source file, for error messages
195	absFilename string // absolute file name, for PC-Line tables
196	line, col   uint   // relative line, column number at pos
197	inl         int    // inlining index (see cmd/internal/obj/inl.go)
198	fileIndex   int    // index of absFilename within PosTable.FileTable
199}
200
201// NewFileBase returns a new *PosBase for a file with the given (relative and
202// absolute) filenames.
203func NewFileBase(filename, absFilename string) *PosBase {
204	base := &PosBase{
205		filename:    filename,
206		absFilename: absFilename,
207		line:        1,
208		col:         1,
209		inl:         -1,
210		fileIndex:   -1,
211	}
212	base.pos = MakePos(base, 1, 1)
213	return base
214}
215
216// NewLinePragmaBase returns a new *PosBase for a line directive of the form
217//
218//	//line filename:line:col
219//	/*line filename:line:col*/
220//
221// at position pos.
222func NewLinePragmaBase(pos Pos, filename, absFilename string, line, col uint) *PosBase {
223	return &PosBase{pos, filename, absFilename, line, col, -1, -1}
224}
225
226// NewInliningBase returns a copy of the orig PosBase with the given inlining
227// index. If orig == nil, NewInliningBase panics.
228func NewInliningBase(orig *PosBase, inlTreeIndex int) *PosBase {
229	if orig == nil {
230		panic("no old PosBase")
231	}
232	base := *orig
233	base.inl = inlTreeIndex
234	base.fileIndex = -1
235	if orig == orig.pos.base {
236		base.pos.base = &base
237	}
238	return &base
239}
240
241var noPos Pos
242
243// Pos returns the position at which base is located.
244// If b == nil, the result is the zero position.
245func (b *PosBase) Pos() *Pos {
246	if b != nil {
247		return &b.pos
248	}
249	return &noPos
250}
251
252// Filename returns the filename recorded with the base.
253// If b == nil, the result is the empty string.
254func (b *PosBase) Filename() string {
255	if b != nil {
256		return b.filename
257	}
258	return ""
259}
260
261// AbsFilename returns the absolute filename recorded with the base.
262// If b == nil, the result is the empty string.
263func (b *PosBase) AbsFilename() string {
264	if b != nil {
265		return b.absFilename
266	}
267	return ""
268}
269
270// FileSymPrefix is the linker symbol prefix that used to be used for
271// linker pseudo-symbols representing file names.
272const FileSymPrefix = "gofile.."
273
274// FileIndex returns the index of the base's absolute filename within
275// its PosTable's FileTable. It panics if it hasn't been registered
276// with a PosTable. If b == nil, the result is -1.
277func (b *PosBase) FileIndex() int {
278	if b != nil {
279		if b.fileIndex < 0 {
280			panic("PosBase has no file index")
281		}
282		return b.fileIndex
283	}
284	return -1
285}
286
287// Line returns the line number recorded with the base.
288// If b == nil, the result is 0.
289func (b *PosBase) Line() uint {
290	if b != nil {
291		return b.line
292	}
293	return 0
294}
295
296// Col returns the column number recorded with the base.
297// If b == nil, the result is 0.
298func (b *PosBase) Col() uint {
299	if b != nil {
300		return b.col
301	}
302	return 0
303}
304
305// InliningIndex returns the index into the global inlining
306// tree recorded with the base. If b == nil or the base has
307// not been inlined, the result is < 0.
308func (b *PosBase) InliningIndex() int {
309	if b != nil {
310		return b.inl
311	}
312	return -1
313}
314
315// ----------------------------------------------------------------------------
316// lico
317
318// A lico is a compact encoding of a LIne and COlumn number.
319type lico uint32
320
321// Layout constants: 20 bits for line, 8 bits for column, 2 for isStmt, 2 for pro/epilogue
322// (If this is too tight, we can either make lico 64b wide,
323// or we can introduce a tiered encoding where we remove column
324// information as line numbers grow bigger; similar to what gcc
325// does.)
326// The bitfield order is chosen to make IsStmt be the least significant
327// part of a position; its use is to communicate statement edges through
328// instruction scrambling in code generation, not to impose an order.
329// TODO: Prologue and epilogue are perhaps better handled as pseudo-ops for the assembler,
330// because they have almost no interaction with other uses of the position.
331const (
332	lineBits, lineMax     = 20, 1<<lineBits - 2
333	bogusLine             = 1 // Used to disrupt infinite loops to prevent debugger looping
334	isStmtBits, isStmtMax = 2, 1<<isStmtBits - 1
335	xlogueBits, xlogueMax = 2, 1<<xlogueBits - 1
336	colBits, colMax       = 32 - lineBits - xlogueBits - isStmtBits, 1<<colBits - 1
337
338	isStmtShift = 0
339	isStmtMask  = isStmtMax << isStmtShift
340	xlogueShift = isStmtBits + isStmtShift
341	xlogueMask  = xlogueMax << xlogueShift
342	colShift    = xlogueBits + xlogueShift
343	lineShift   = colBits + colShift
344)
345const (
346	// It is expected that the front end or a phase in SSA will usually generate positions tagged with
347	// PosDefaultStmt, but note statement boundaries with PosIsStmt.  Simple statements will have a single
348	// boundary; for loops with initialization may have one for their entry and one for their back edge
349	// (this depends on exactly how the loop is compiled; the intent is to provide a good experience to a
350	// user debugging a program; the goal is that a breakpoint set on the loop line fires both on entry
351	// and on iteration).  Proper treatment of non-gofmt input with multiple simple statements on a single
352	// line is TBD.
353	//
354	// Optimizing compilation will move instructions around, and some of these will become known-bad as
355	// step targets for debugging purposes (examples: register spills and reloads; code generated into
356	// the entry block; invariant code hoisted out of loops) but those instructions will still have interesting
357	// positions for profiling purposes. To reflect this these positions will be changed to PosNotStmt.
358	//
359	// When the optimizer removes an instruction marked PosIsStmt; it should attempt to find a nearby
360	// instruction with the same line marked PosDefaultStmt to be the new statement boundary.  I.e., the
361	// optimizer should make a best-effort to conserve statement boundary positions, and might be enhanced
362	// to note when a statement boundary is not conserved.
363	//
364	// Code cloning, e.g. loop unrolling or loop unswitching, is an exception to the conservation rule
365	// because a user running a debugger would expect to see breakpoints active in the copies of the code.
366	//
367	// In non-optimizing compilation there is still a role for PosNotStmt because of code generation
368	// into the entry block.  PosIsStmt statement positions should be conserved.
369	//
370	// When code generation occurs any remaining default-marked positions are replaced with not-statement
371	// positions.
372	//
373	PosDefaultStmt uint = iota // Default; position is not a statement boundary, but might be if optimization removes the designated statement boundary
374	PosIsStmt                  // Position is a statement boundary; if optimization removes the corresponding instruction, it should attempt to find a new instruction to be the boundary.
375	PosNotStmt                 // Position should not be a statement boundary, but line should be preserved for profiling and low-level debugging purposes.
376)
377
378type PosXlogue uint
379
380const (
381	PosDefaultLogue PosXlogue = iota
382	PosPrologueEnd
383	PosEpilogueBegin
384)
385
386func makeLicoRaw(line, col uint) lico {
387	return lico(line<<lineShift | col<<colShift)
388}
389
390// This is a not-position that will not be elided.
391// Depending on the debugger (gdb or delve) it may or may not be displayed.
392func makeBogusLico() lico {
393	return makeLicoRaw(bogusLine, 0).withIsStmt()
394}
395
396func makeLico(line, col uint) lico {
397	if line >= lineMax {
398		// cannot represent line, use max. line so we have some information
399		line = lineMax
400		// Drop column information if line number saturates.
401		// Ensures line+col is monotonic. See issue 51193.
402		col = 0
403	}
404	if col > colMax {
405		// cannot represent column, use max. column so we have some information
406		col = colMax
407	}
408	// default is not-sure-if-statement
409	return makeLicoRaw(line, col)
410}
411
412func (x lico) Line() uint           { return uint(x) >> lineShift }
413func (x lico) SameLine(y lico) bool { return 0 == (x^y)&^lico(1<<lineShift-1) }
414func (x lico) Col() uint            { return uint(x) >> colShift & colMax }
415func (x lico) IsStmt() uint {
416	if x == 0 {
417		return PosNotStmt
418	}
419	return uint(x) >> isStmtShift & isStmtMax
420}
421func (x lico) Xlogue() PosXlogue {
422	return PosXlogue(uint(x) >> xlogueShift & xlogueMax)
423}
424
425// withNotStmt returns a lico for the same location, but not a statement
426func (x lico) withNotStmt() lico {
427	return x.withStmt(PosNotStmt)
428}
429
430// withDefaultStmt returns a lico for the same location, with default isStmt
431func (x lico) withDefaultStmt() lico {
432	return x.withStmt(PosDefaultStmt)
433}
434
435// withIsStmt returns a lico for the same location, tagged as definitely a statement
436func (x lico) withIsStmt() lico {
437	return x.withStmt(PosIsStmt)
438}
439
440// withXlogue attaches a prologue/epilogue attribute to a lico
441func (x lico) withXlogue(xlogue PosXlogue) lico {
442	if x == 0 {
443		if xlogue == 0 {
444			return x
445		}
446		// Normalize 0 to "not a statement"
447		x = lico(PosNotStmt << isStmtShift)
448	}
449	return lico(uint(x) & ^uint(xlogueMax<<xlogueShift) | (uint(xlogue) << xlogueShift))
450}
451
452// withStmt returns a lico for the same location with specified is_stmt attribute
453func (x lico) withStmt(stmt uint) lico {
454	if x == 0 {
455		return lico(0)
456	}
457	return lico(uint(x) & ^uint(isStmtMax<<isStmtShift) | (stmt << isStmtShift))
458}
459
460func (x lico) lineNumber() string {
461	return fmt.Sprintf("%d", x.Line())
462}
463
464func (x lico) lineNumberHTML() string {
465	if x.IsStmt() == PosDefaultStmt {
466		return fmt.Sprintf("%d", x.Line())
467	}
468	style, pfx := "b", "+"
469	if x.IsStmt() == PosNotStmt {
470		style = "s" // /strike not supported in HTML5
471		pfx = ""
472	}
473	return fmt.Sprintf("<%s>%s%d</%s>", style, pfx, x.Line(), style)
474}
475
476func (x lico) atColumn1() lico {
477	return makeLico(x.Line(), 1).withIsStmt()
478}
479