1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 package report
16
17
18
19
20 import (
21 "bufio"
22 "fmt"
23 "html/template"
24 "io"
25 "os"
26 "path/filepath"
27 "regexp"
28 "slices"
29 "sort"
30 "strconv"
31 "strings"
32
33 "github.com/google/pprof/internal/graph"
34 "github.com/google/pprof/internal/measurement"
35 "github.com/google/pprof/internal/plugin"
36 "github.com/google/pprof/profile"
37 )
38
39
40
41
42
43 func printSource(w io.Writer, rpt *Report) error {
44 o := rpt.options
45 g := rpt.newGraph(nil)
46
47
48
49 var functions graph.Nodes
50 functionNodes := make(map[string]graph.Nodes)
51 for _, n := range g.Nodes {
52 if !o.Symbol.MatchString(n.Info.Name) {
53 continue
54 }
55 if functionNodes[n.Info.Name] == nil {
56 functions = append(functions, n)
57 }
58 functionNodes[n.Info.Name] = append(functionNodes[n.Info.Name], n)
59 }
60 functions.Sort(graph.NameOrder)
61
62 if len(functionNodes) == 0 {
63 return fmt.Errorf("no matches found for regexp: %s", o.Symbol)
64 }
65
66 sourcePath := o.SourcePath
67 if sourcePath == "" {
68 wd, err := os.Getwd()
69 if err != nil {
70 return fmt.Errorf("could not stat current dir: %v", err)
71 }
72 sourcePath = wd
73 }
74 reader := newSourceReader(sourcePath, o.TrimPath)
75
76 fmt.Fprintf(w, "Total: %s\n", rpt.formatValue(rpt.total))
77 for _, fn := range functions {
78 name := fn.Info.Name
79
80
81
82 var sourceFiles graph.Nodes
83 fileNodes := make(map[string]graph.Nodes)
84 for _, n := range functionNodes[name] {
85 if n.Info.File == "" {
86 continue
87 }
88 if fileNodes[n.Info.File] == nil {
89 sourceFiles = append(sourceFiles, n)
90 }
91 fileNodes[n.Info.File] = append(fileNodes[n.Info.File], n)
92 }
93
94 if len(sourceFiles) == 0 {
95 fmt.Fprintf(w, "No source information for %s\n", name)
96 continue
97 }
98
99 sourceFiles.Sort(graph.FileOrder)
100
101
102 for _, fl := range sourceFiles {
103 filename := fl.Info.File
104 fns := fileNodes[filename]
105 flatSum, cumSum := fns.Sum()
106
107 fnodes, _, err := getSourceFromFile(filename, reader, fns, 0, 0)
108 fmt.Fprintf(w, "ROUTINE ======================== %s in %s\n", name, filename)
109 fmt.Fprintf(w, "%10s %10s (flat, cum) %s of Total\n",
110 rpt.formatValue(flatSum), rpt.formatValue(cumSum),
111 measurement.Percentage(cumSum, rpt.total))
112
113 if err != nil {
114 fmt.Fprintf(w, " Error: %v\n", err)
115 continue
116 }
117
118 for _, fn := range fnodes {
119 fmt.Fprintf(w, "%10s %10s %6d:%s\n", valueOrDot(fn.Flat, rpt), valueOrDot(fn.Cum, rpt), fn.Info.Lineno, fn.Info.Name)
120 }
121 }
122 }
123 return nil
124 }
125
126
127 type sourcePrinter struct {
128 reader *sourceReader
129 synth *synthCode
130 objectTool plugin.ObjTool
131 objects map[string]plugin.ObjFile
132 sym *regexp.Regexp
133 files map[string]*sourceFile
134 insts map[uint64]instructionInfo
135
136
137
138 interest map[string]bool
139
140
141 prettyNames map[string]string
142 }
143
144
145 type addrInfo struct {
146 loc *profile.Location
147 obj plugin.ObjFile
148 }
149
150
151 type instructionInfo struct {
152 objAddr uint64
153 length int
154 disasm string
155 file string
156 line int
157 flat, cum int64
158 }
159
160
161 type sourceFile struct {
162 fname string
163 cum int64
164 flat int64
165 lines map[int][]sourceInst
166 funcName map[int]string
167 }
168
169
170 type sourceInst struct {
171 addr uint64
172 stack []callID
173 }
174
175
176
177 type sourceFunction struct {
178 name string
179 begin, end int
180 flat, cum int64
181 }
182
183
184 type addressRange struct {
185 begin, end uint64
186 obj plugin.ObjFile
187 mapping *profile.Mapping
188 score int64
189 }
190
191
192 type WebListData struct {
193 Total string
194 Files []WebListFile
195 }
196
197
198 type WebListFile struct {
199 Funcs []WebListFunc
200 }
201
202
203 type WebListFunc struct {
204 Name string
205 File string
206 Flat string
207 Cumulative string
208 Percent string
209 Lines []WebListLine
210 }
211
212
213 type WebListLine struct {
214 SrcLine string
215 HTMLClass string
216 Line int
217 Flat string
218 Cumulative string
219 Instructions []WebListInstruction
220 }
221
222
223 type WebListInstruction struct {
224 NewBlock bool
225 Flat string
226 Cumulative string
227 Synthetic bool
228 Address uint64
229 Disasm string
230 FileLine string
231 InlinedCalls []WebListCall
232 }
233
234
235 type WebListCall struct {
236 SrcLine string
237 FileBase string
238 Line int
239 }
240
241
242
243 func MakeWebList(rpt *Report, obj plugin.ObjTool, maxFiles int) (WebListData, error) {
244 sourcePath := rpt.options.SourcePath
245 if sourcePath == "" {
246 wd, err := os.Getwd()
247 if err != nil {
248 return WebListData{}, fmt.Errorf("could not stat current dir: %v", err)
249 }
250 sourcePath = wd
251 }
252 sp := newSourcePrinter(rpt, obj, sourcePath)
253 if len(sp.interest) == 0 {
254 return WebListData{}, fmt.Errorf("no matches found for regexp: %s", rpt.options.Symbol)
255 }
256 defer sp.close()
257 return sp.generate(maxFiles, rpt), nil
258 }
259
260 func newSourcePrinter(rpt *Report, obj plugin.ObjTool, sourcePath string) *sourcePrinter {
261 sp := &sourcePrinter{
262 reader: newSourceReader(sourcePath, rpt.options.TrimPath),
263 synth: newSynthCode(rpt.prof.Mapping),
264 objectTool: obj,
265 objects: map[string]plugin.ObjFile{},
266 sym: rpt.options.Symbol,
267 files: map[string]*sourceFile{},
268 insts: map[uint64]instructionInfo{},
269 prettyNames: map[string]string{},
270 interest: map[string]bool{},
271 }
272
273
274
275 var address *uint64
276 if sp.sym != nil {
277 if hex, err := strconv.ParseUint(sp.sym.String(), 0, 64); err == nil {
278 address = &hex
279 }
280 }
281
282 addrs := map[uint64]addrInfo{}
283 flat := map[uint64]int64{}
284 cum := map[uint64]int64{}
285
286
287 markInterest := func(addr uint64, loc *profile.Location, index int) {
288 fn := loc.Line[index]
289 if fn.Function == nil {
290 return
291 }
292 sp.interest[fn.Function.Name] = true
293 sp.interest[fn.Function.SystemName] = true
294 if _, ok := addrs[addr]; !ok {
295 addrs[addr] = addrInfo{loc, sp.objectFile(loc.Mapping)}
296 }
297 }
298
299
300 matches := func(line profile.Line) bool {
301 if line.Function == nil {
302 return false
303 }
304 return sp.sym.MatchString(line.Function.Name) ||
305 sp.sym.MatchString(line.Function.SystemName) ||
306 sp.sym.MatchString(line.Function.Filename)
307 }
308
309
310 for _, sample := range rpt.prof.Sample {
311 value := rpt.options.SampleValue(sample.Value)
312 if rpt.options.SampleMeanDivisor != nil {
313 div := rpt.options.SampleMeanDivisor(sample.Value)
314 if div != 0 {
315 value /= div
316 }
317 }
318
319
320 for i := len(sample.Location) - 1; i >= 0; i-- {
321 loc := sample.Location[i]
322 for _, line := range loc.Line {
323 if line.Function == nil {
324 continue
325 }
326 sp.prettyNames[line.Function.SystemName] = line.Function.Name
327 }
328
329 addr := loc.Address
330 if addr == 0 {
331
332 addr = sp.synth.address(loc)
333 }
334
335 cum[addr] += value
336 if i == 0 {
337 flat[addr] += value
338 }
339
340 if sp.sym == nil || (address != nil && addr == *address) {
341
342 if len(loc.Line) > 0 {
343 markInterest(addr, loc, len(loc.Line)-1)
344 }
345 continue
346 }
347
348
349 matchFile := (loc.Mapping != nil && sp.sym.MatchString(loc.Mapping.File))
350 for j, line := range loc.Line {
351 if (j == 0 && matchFile) || matches(line) {
352 markInterest(addr, loc, j)
353 }
354 }
355 }
356 }
357
358 sp.expandAddresses(rpt, addrs, flat)
359 sp.initSamples(flat, cum)
360 return sp
361 }
362
363 func (sp *sourcePrinter) close() {
364 for _, objFile := range sp.objects {
365 if objFile != nil {
366 objFile.Close()
367 }
368 }
369 }
370
371 func (sp *sourcePrinter) expandAddresses(rpt *Report, addrs map[uint64]addrInfo, flat map[uint64]int64) {
372
373
374 ranges, unprocessed := sp.splitIntoRanges(rpt.prof, addrs, flat)
375 sp.handleUnprocessed(addrs, unprocessed)
376
377
378 const maxRanges = 25
379 sort.Slice(ranges, func(i, j int) bool {
380 return ranges[i].score > ranges[j].score
381 })
382 if len(ranges) > maxRanges {
383 ranges = ranges[:maxRanges]
384 }
385
386 for _, r := range ranges {
387 objBegin, err := r.obj.ObjAddr(r.begin)
388 if err != nil {
389 fmt.Fprintf(os.Stderr, "Failed to compute objdump address for range start %x: %v\n", r.begin, err)
390 continue
391 }
392 objEnd, err := r.obj.ObjAddr(r.end)
393 if err != nil {
394 fmt.Fprintf(os.Stderr, "Failed to compute objdump address for range end %x: %v\n", r.end, err)
395 continue
396 }
397 base := r.begin - objBegin
398 insts, err := sp.objectTool.Disasm(r.mapping.File, objBegin, objEnd, rpt.options.IntelSyntax)
399 if err != nil {
400
401 continue
402 }
403
404 var lastFrames []plugin.Frame
405 var lastAddr, maxAddr uint64
406 for i, inst := range insts {
407 addr := inst.Addr + base
408
409
410 if addr <= maxAddr {
411 continue
412 }
413 maxAddr = addr
414
415 length := 1
416 if i+1 < len(insts) && insts[i+1].Addr > inst.Addr {
417
418 length = int(insts[i+1].Addr - inst.Addr)
419 }
420
421
422 frames, err := r.obj.SourceLine(addr)
423 if err != nil {
424
425 frames = []plugin.Frame{{Func: inst.Function, File: inst.File, Line: inst.Line}}
426 }
427
428 x := instructionInfo{objAddr: inst.Addr, length: length, disasm: inst.Text}
429 if len(frames) > 0 {
430
431
432
433
434
435
436
437
438
439
440
441
442
443 index := 0
444 x.file = frames[index].File
445 x.line = frames[index].Line
446 }
447 sp.insts[addr] = x
448
449
450
451
452 const neighborhood = 32
453 if len(frames) > 0 && frames[0].Line != 0 {
454 lastFrames = frames
455 lastAddr = addr
456 } else if (addr-lastAddr <= neighborhood) && lastFrames != nil {
457 frames = lastFrames
458 }
459
460 sp.addStack(addr, frames)
461 }
462 }
463 }
464
465 func (sp *sourcePrinter) addStack(addr uint64, frames []plugin.Frame) {
466
467 for i, f := range frames {
468 if !sp.interest[f.Func] {
469 continue
470 }
471
472
473 fname := canonicalizeFileName(f.File)
474 file := sp.files[fname]
475 if file == nil {
476 file = &sourceFile{
477 fname: fname,
478 lines: map[int][]sourceInst{},
479 funcName: map[int]string{},
480 }
481 sp.files[fname] = file
482 }
483 callees := frames[:i]
484 stack := make([]callID, 0, len(callees))
485 for j := len(callees) - 1; j >= 0; j-- {
486 stack = append(stack, callID{
487 file: callees[j].File,
488 line: callees[j].Line,
489 })
490 }
491 file.lines[f.Line] = append(file.lines[f.Line], sourceInst{addr, stack})
492
493
494
495 if _, ok := file.funcName[f.Line]; !ok {
496 file.funcName[f.Line] = f.Func
497 }
498 }
499 }
500
501
502 const synthAsm = ""
503
504
505
506 func (sp *sourcePrinter) handleUnprocessed(addrs map[uint64]addrInfo, unprocessed []uint64) {
507
508
509
510 makeFrames := func(addr uint64) []plugin.Frame {
511 loc := addrs[addr].loc
512 stack := make([]plugin.Frame, 0, len(loc.Line))
513 for _, line := range loc.Line {
514 fn := line.Function
515 if fn == nil {
516 continue
517 }
518 stack = append(stack, plugin.Frame{
519 Func: fn.Name,
520 File: fn.Filename,
521 Line: int(line.Line),
522 })
523 }
524 return stack
525 }
526
527 for _, addr := range unprocessed {
528 frames := makeFrames(addr)
529 x := instructionInfo{
530 objAddr: addr,
531 length: 1,
532 disasm: synthAsm,
533 }
534 if len(frames) > 0 {
535 x.file = frames[0].File
536 x.line = frames[0].Line
537 }
538 sp.insts[addr] = x
539
540 sp.addStack(addr, frames)
541 }
542 }
543
544
545
546
547 func (sp *sourcePrinter) splitIntoRanges(prof *profile.Profile, addrMap map[uint64]addrInfo, flat map[uint64]int64) ([]addressRange, []uint64) {
548
549 var addrs, unprocessed []uint64
550 for addr, info := range addrMap {
551 if info.obj != nil {
552 addrs = append(addrs, addr)
553 } else {
554 unprocessed = append(unprocessed, addr)
555 }
556 }
557 slices.Sort(addrs)
558
559 const expand = 500
560 var result []addressRange
561 for i, n := 0, len(addrs); i < n; {
562 begin, end := addrs[i], addrs[i]
563 sum := flat[begin]
564 i++
565
566 info := addrMap[begin]
567 m := info.loc.Mapping
568 obj := info.obj
569
570
571 for i < n && addrs[i] <= end+2*expand && addrs[i] < m.Limit {
572
573
574 end = addrs[i]
575 sum += flat[end]
576 i++
577 }
578 if m.Start-begin >= expand {
579 begin -= expand
580 } else {
581 begin = m.Start
582 }
583 if m.Limit-end >= expand {
584 end += expand
585 } else {
586 end = m.Limit
587 }
588
589 result = append(result, addressRange{begin, end, obj, m, sum})
590 }
591 return result, unprocessed
592 }
593
594 func (sp *sourcePrinter) initSamples(flat, cum map[uint64]int64) {
595 for addr, inst := range sp.insts {
596
597
598
599 instEnd := addr + uint64(inst.length)
600 for p := addr; p < instEnd; p++ {
601 inst.flat += flat[p]
602 inst.cum += cum[p]
603 }
604 sp.insts[addr] = inst
605 }
606 }
607
608 func (sp *sourcePrinter) generate(maxFiles int, rpt *Report) WebListData {
609
610 for _, file := range sp.files {
611 seen := map[uint64]bool{}
612 for _, line := range file.lines {
613 for _, x := range line {
614 if seen[x.addr] {
615
616
617
618 continue
619 }
620 seen[x.addr] = true
621 inst := sp.insts[x.addr]
622 file.cum += inst.cum
623 file.flat += inst.flat
624 }
625 }
626 }
627
628
629 var files []*sourceFile
630 for _, f := range sp.files {
631 files = append(files, f)
632 }
633 order := func(i, j int) bool { return files[i].flat > files[j].flat }
634 if maxFiles < 0 {
635
636 order = func(i, j int) bool { return files[i].fname < files[j].fname }
637 maxFiles = len(files)
638 }
639 sort.Slice(files, order)
640 result := WebListData{
641 Total: rpt.formatValue(rpt.total),
642 }
643 for i, f := range files {
644 if i < maxFiles {
645 result.Files = append(result.Files, sp.generateFile(f, rpt))
646 }
647 }
648 return result
649 }
650
651 func (sp *sourcePrinter) generateFile(f *sourceFile, rpt *Report) WebListFile {
652 var result WebListFile
653 for _, fn := range sp.functions(f) {
654 if fn.cum == 0 {
655 continue
656 }
657
658 listfn := WebListFunc{
659 Name: fn.name,
660 File: f.fname,
661 Flat: rpt.formatValue(fn.flat),
662 Cumulative: rpt.formatValue(fn.cum),
663 Percent: measurement.Percentage(fn.cum, rpt.total),
664 }
665 var asm []assemblyInstruction
666 for l := fn.begin; l < fn.end; l++ {
667 lineContents, ok := sp.reader.line(f.fname, l)
668 if !ok {
669 if len(f.lines[l]) == 0 {
670
671 continue
672 }
673 if l == 0 {
674
675 lineContents = "<instructions with unknown line numbers>"
676 } else {
677
678 lineContents = "???"
679 }
680 }
681
682
683 asm = asm[:0]
684 var flatSum, cumSum int64
685 var lastAddr uint64
686 for _, inst := range f.lines[l] {
687 addr := inst.addr
688 x := sp.insts[addr]
689 flatSum += x.flat
690 cumSum += x.cum
691 startsBlock := (addr != lastAddr+uint64(sp.insts[lastAddr].length))
692 lastAddr = addr
693
694
695 asm = append(asm, assemblyInstruction{
696 address: x.objAddr,
697 instruction: x.disasm,
698 function: fn.name,
699 file: x.file,
700 line: x.line,
701 flat: x.flat,
702 cum: x.cum,
703 startsBlock: startsBlock,
704 inlineCalls: inst.stack,
705 })
706 }
707
708 listfn.Lines = append(listfn.Lines, makeWebListLine(l, flatSum, cumSum, lineContents, asm, sp.reader, rpt))
709 }
710
711 result.Funcs = append(result.Funcs, listfn)
712 }
713 return result
714 }
715
716
717 func (sp *sourcePrinter) functions(f *sourceFile) []sourceFunction {
718 var funcs []sourceFunction
719
720
721 lines := make([]int, 0, len(f.lines))
722 for l := range f.lines {
723 lines = append(lines, l)
724 }
725 sort.Ints(lines)
726
727
728 const mergeLimit = 20
729 for _, l := range lines {
730 name := f.funcName[l]
731 if pretty, ok := sp.prettyNames[name]; ok {
732
733 name = pretty
734 }
735
736 fn := sourceFunction{name: name, begin: l, end: l + 1}
737 for _, x := range f.lines[l] {
738 inst := sp.insts[x.addr]
739 fn.flat += inst.flat
740 fn.cum += inst.cum
741 }
742
743
744 if len(funcs) > 0 {
745 last := funcs[len(funcs)-1]
746 if l-last.end < mergeLimit && last.name == name {
747 last.end = l + 1
748 last.flat += fn.flat
749 last.cum += fn.cum
750 funcs[len(funcs)-1] = last
751 continue
752 }
753 }
754
755
756 funcs = append(funcs, fn)
757 }
758
759
760 const expand = 5
761 for i, f := range funcs {
762 if i == 0 {
763
764
765
766 if f.begin > expand {
767 f.begin -= expand
768 } else if f.begin > 1 {
769 f.begin = 1
770 }
771 } else {
772
773 halfGap := min((f.begin-funcs[i-1].end)/2, expand)
774 funcs[i-1].end += halfGap
775 f.begin -= halfGap
776 }
777 funcs[i] = f
778 }
779
780
781 if len(funcs) > 0 {
782 funcs[len(funcs)-1].end += expand
783 }
784
785 return funcs
786 }
787
788
789
790 func (sp *sourcePrinter) objectFile(m *profile.Mapping) plugin.ObjFile {
791 if m == nil {
792 return nil
793 }
794 if object, ok := sp.objects[m.File]; ok {
795 return object
796 }
797 object, err := sp.objectTool.Open(m.File, m.Start, m.Limit, m.Offset, m.KernelRelocationSymbol)
798 if err != nil {
799 object = nil
800 }
801 sp.objects[m.File] = object
802 return object
803 }
804
805
806
807 func makeWebListLine(lineNo int, flat, cum int64, lineContents string,
808 assembly []assemblyInstruction, reader *sourceReader, rpt *Report) WebListLine {
809 line := WebListLine{
810 SrcLine: lineContents,
811 Line: lineNo,
812 Flat: valueOrDot(flat, rpt),
813 Cumulative: valueOrDot(cum, rpt),
814 }
815
816 if len(assembly) == 0 {
817 line.HTMLClass = "nop"
818 return line
819 }
820
821 nestedInfo := false
822 line.HTMLClass = "deadsrc"
823 for _, an := range assembly {
824 if len(an.inlineCalls) > 0 || an.instruction != synthAsm {
825 nestedInfo = true
826 line.HTMLClass = "livesrc"
827 }
828 }
829
830 if nestedInfo {
831 srcIndent := indentation(lineContents)
832 line.Instructions = makeWebListInstructions(srcIndent, assembly, reader, rpt)
833 }
834 return line
835 }
836
837 func makeWebListInstructions(srcIndent int, assembly []assemblyInstruction, reader *sourceReader, rpt *Report) []WebListInstruction {
838 var result []WebListInstruction
839 var curCalls []callID
840 for i, an := range assembly {
841 var fileline string
842 if an.file != "" {
843 fileline = fmt.Sprintf("%s:%d", template.HTMLEscapeString(filepath.Base(an.file)), an.line)
844 }
845 text := strings.Repeat(" ", srcIndent+4+4*len(an.inlineCalls)) + an.instruction
846 inst := WebListInstruction{
847 NewBlock: (an.startsBlock && i != 0),
848 Flat: valueOrDot(an.flat, rpt),
849 Cumulative: valueOrDot(an.cum, rpt),
850 Synthetic: (an.instruction == synthAsm),
851 Address: an.address,
852 Disasm: rightPad(text, 80),
853 FileLine: fileline,
854 }
855
856
857 for j, c := range an.inlineCalls {
858 if j < len(curCalls) && curCalls[j] == c {
859
860 continue
861 }
862 curCalls = nil
863 fline, ok := reader.line(c.file, c.line)
864 if !ok {
865 fline = ""
866 }
867 srcCode := strings.Repeat(" ", srcIndent+4+4*j) + strings.TrimSpace(fline)
868 inst.InlinedCalls = append(inst.InlinedCalls, WebListCall{
869 SrcLine: rightPad(srcCode, 80),
870 FileBase: filepath.Base(c.file),
871 Line: c.line,
872 })
873 }
874 curCalls = an.inlineCalls
875
876 result = append(result, inst)
877 }
878 return result
879 }
880
881
882
883
884 func getSourceFromFile(file string, reader *sourceReader, fns graph.Nodes, start, end int) (graph.Nodes, string, error) {
885 lineNodes := make(map[int]graph.Nodes)
886
887
888 const margin = 5
889 if start == 0 {
890 if fns[0].Info.StartLine != 0 {
891 start = fns[0].Info.StartLine
892 } else {
893 start = fns[0].Info.Lineno - margin
894 }
895 } else {
896 start -= margin
897 }
898 if end == 0 {
899 end = fns[0].Info.Lineno
900 }
901 end += margin
902 for _, n := range fns {
903 lineno := n.Info.Lineno
904 nodeStart := n.Info.StartLine
905 if nodeStart == 0 {
906 nodeStart = lineno - margin
907 }
908 nodeEnd := lineno + margin
909 if nodeStart < start {
910 start = nodeStart
911 } else if nodeEnd > end {
912 end = nodeEnd
913 }
914 lineNodes[lineno] = append(lineNodes[lineno], n)
915 }
916 if start < 1 {
917 start = 1
918 }
919
920 var src graph.Nodes
921 for lineno := start; lineno <= end; lineno++ {
922 line, ok := reader.line(file, lineno)
923 if !ok {
924 break
925 }
926 flat, cum := lineNodes[lineno].Sum()
927 src = append(src, &graph.Node{
928 Info: graph.NodeInfo{
929 Name: strings.TrimRight(line, "\n"),
930 Lineno: lineno,
931 },
932 Flat: flat,
933 Cum: cum,
934 })
935 }
936 if err := reader.fileError(file); err != nil {
937 return nil, file, err
938 }
939 return src, file, nil
940 }
941
942
943 type sourceReader struct {
944
945
946 searchPath string
947
948
949 trimPath string
950
951
952
953 files map[string][]string
954
955
956
957 errors map[string]error
958 }
959
960 func newSourceReader(searchPath, trimPath string) *sourceReader {
961 return &sourceReader{
962 searchPath,
963 trimPath,
964 make(map[string][]string),
965 make(map[string]error),
966 }
967 }
968
969 func (reader *sourceReader) fileError(path string) error {
970 return reader.errors[path]
971 }
972
973
974 func (reader *sourceReader) line(path string, lineno int) (string, bool) {
975 lines, ok := reader.files[path]
976 if !ok {
977
978 lines = []string{""}
979 f, err := openSourceFile(path, reader.searchPath, reader.trimPath)
980 if err != nil {
981 reader.errors[path] = err
982 } else {
983 s := bufio.NewScanner(f)
984 for s.Scan() {
985 lines = append(lines, s.Text())
986 }
987 f.Close()
988 if s.Err() != nil {
989 reader.errors[path] = err
990 }
991 }
992 reader.files[path] = lines
993 }
994 if lineno <= 0 || lineno >= len(lines) {
995 return "", false
996 }
997 return lines[lineno], true
998 }
999
1000
1001
1002
1003
1004
1005
1006 func openSourceFile(path, searchPath, trim string) (*os.File, error) {
1007 path = trimPath(path, trim, searchPath)
1008
1009 if filepath.IsAbs(path) {
1010 f, err := os.Open(path)
1011 return f, err
1012 }
1013
1014 for _, dir := range filepath.SplitList(searchPath) {
1015
1016 for {
1017 filename := filepath.Join(dir, path)
1018 if f, err := os.Open(filename); err == nil {
1019 return f, nil
1020 }
1021 parent := filepath.Dir(dir)
1022 if parent == dir {
1023 break
1024 }
1025 dir = parent
1026 }
1027 }
1028
1029 return nil, fmt.Errorf("could not find file %s on path %s", path, searchPath)
1030 }
1031
1032
1033
1034
1035
1036 func trimPath(path, trimPath, searchPath string) string {
1037
1038 sPath, searchPath := filepath.ToSlash(path), filepath.ToSlash(searchPath)
1039 if trimPath == "" {
1040
1041
1042
1043
1044
1045
1046 for _, dir := range filepath.SplitList(searchPath) {
1047 want := "/" + filepath.Base(dir) + "/"
1048 if found := strings.Index(sPath, want); found != -1 {
1049 return path[found+len(want):]
1050 }
1051 }
1052 }
1053
1054 trimPaths := append(filepath.SplitList(filepath.ToSlash(trimPath)), "/proc/self/cwd/./", "/proc/self/cwd/")
1055 for _, trimPath := range trimPaths {
1056 if !strings.HasSuffix(trimPath, "/") {
1057 trimPath += "/"
1058 }
1059 if strings.HasPrefix(sPath, trimPath) {
1060 return path[len(trimPath):]
1061 }
1062 }
1063 return path
1064 }
1065
1066 func indentation(line string) int {
1067 column := 0
1068 for _, c := range line {
1069 if c == ' ' {
1070 column++
1071 } else if c == '\t' {
1072 column++
1073 for column%8 != 0 {
1074 column++
1075 }
1076 } else {
1077 break
1078 }
1079 }
1080 return column
1081 }
1082
1083
1084
1085
1086 func rightPad(s string, n int) string {
1087 var str strings.Builder
1088
1089
1090
1091 column := 0
1092 for _, c := range s {
1093 column++
1094 if c == '\t' {
1095 str.WriteRune(' ')
1096 for column%8 != 0 {
1097 column++
1098 str.WriteRune(' ')
1099 }
1100 } else {
1101 str.WriteRune(c)
1102 }
1103 }
1104 for column < n {
1105 column++
1106 str.WriteRune(' ')
1107 }
1108 return str.String()
1109 }
1110
1111 func canonicalizeFileName(fname string) string {
1112 fname = strings.TrimPrefix(fname, "/proc/self/cwd/")
1113 fname = strings.TrimPrefix(fname, "./")
1114 return filepath.Clean(fname)
1115 }
1116
View as plain text