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