]> Cypherpunks repositories - gostls13.git/commitdiff
cmd/pprof: update vendored github.com/google/pprof
authorDmitri Shuralyov <dmitshur@golang.org>
Tue, 15 Aug 2023 15:45:57 +0000 (11:45 -0400)
committerGopher Robot <gobot@golang.org>
Tue, 15 Aug 2023 16:39:48 +0000 (16:39 +0000)
Pull in the latest published version of github.com/google/pprof
as part of the continuous process of keeping Go's dependencies
up to date. Done with:

go get github.com/google/pprof
go mod tidy
go mod vendor

For #36905.

Change-Id: I2a48e912712bc916c9d749acb1550682f919477e
Reviewed-on: https://go-review.googlesource.com/c/go/+/519657
Reviewed-by: Dmitri Shuralyov <dmitshur@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
Reviewed-by: Russ Cox <rsc@golang.org>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Auto-Submit: Dmitri Shuralyov <dmitshur@golang.org>
Run-TryBot: Dmitri Shuralyov <dmitshur@golang.org>

24 files changed:
src/cmd/go.mod
src/cmd/go.sum
src/cmd/vendor/github.com/google/pprof/internal/binutils/addr2liner.go
src/cmd/vendor/github.com/google/pprof/internal/binutils/addr2liner_llvm.go
src/cmd/vendor/github.com/google/pprof/internal/binutils/disasm.go
src/cmd/vendor/github.com/google/pprof/internal/driver/cli.go
src/cmd/vendor/github.com/google/pprof/internal/driver/fetch.go
src/cmd/vendor/github.com/google/pprof/internal/driver/html/common.js
src/cmd/vendor/github.com/google/pprof/internal/driver/html/header.html
src/cmd/vendor/github.com/google/pprof/internal/driver/html/stacks.css
src/cmd/vendor/github.com/google/pprof/internal/driver/html/stacks.js
src/cmd/vendor/github.com/google/pprof/internal/driver/webui.go
src/cmd/vendor/github.com/google/pprof/internal/graph/graph.go
src/cmd/vendor/github.com/google/pprof/internal/measurement/measurement.go
src/cmd/vendor/github.com/google/pprof/internal/report/package.go
src/cmd/vendor/github.com/google/pprof/internal/report/report.go
src/cmd/vendor/github.com/google/pprof/internal/symbolizer/symbolizer.go
src/cmd/vendor/github.com/google/pprof/profile/encode.go
src/cmd/vendor/github.com/google/pprof/profile/profile.go
src/cmd/vendor/github.com/ianlancetaylor/demangle/SECURITY.md [new file with mode: 0644]
src/cmd/vendor/github.com/ianlancetaylor/demangle/ast.go
src/cmd/vendor/github.com/ianlancetaylor/demangle/demangle.go
src/cmd/vendor/github.com/ianlancetaylor/demangle/rust.go
src/cmd/vendor/modules.txt

index d5870db501fc133bd61248dcfa07380af1a20ff1..73363bf851db61822fae238181f6c44314d31f59 100644 (file)
@@ -3,7 +3,7 @@ module cmd
 go 1.22
 
 require (
-       github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26
+       github.com/google/pprof v0.0.0-20230811205829-9131a7e9cc17
        golang.org/x/arch v0.4.0
        golang.org/x/mod v0.12.0
        golang.org/x/sync v0.3.0
@@ -12,4 +12,4 @@ require (
        golang.org/x/tools v0.12.1-0.20230809190736-59fd05da6bc1
 )
 
-require github.com/ianlancetaylor/demangle v0.0.0-20220319035150-800ac71e25c2 // indirect
+require github.com/ianlancetaylor/demangle v0.0.0-20230524184225-eabc099b10ab // indirect
index 5cb7a32fec6e2246b04d907bfa37c7273bbd2302..9acf4e1c20bd5f4c42de99789b26c4e2ca7ac689 100644 (file)
@@ -1,7 +1,7 @@
-github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 h1:Xim43kblpZXfIBQsbuBVKCudVG457BR2GZFIz3uw3hQ=
-github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26/go.mod h1:dDKJzRmX4S37WGHujM7tX//fmj1uioxKzKxz3lo4HJo=
-github.com/ianlancetaylor/demangle v0.0.0-20220319035150-800ac71e25c2 h1:rcanfLhLDA8nozr/K289V1zcntHr3V+SHlXwzz1ZI2g=
-github.com/ianlancetaylor/demangle v0.0.0-20220319035150-800ac71e25c2/go.mod h1:aYm2/VgdVmcIU8iMfdMvDMsRAQjcfZSKFby6HOFvi/w=
+github.com/google/pprof v0.0.0-20230811205829-9131a7e9cc17 h1:0h35ESZ02+hN/MFZb7XZOXg+Rl9+Rk8fBIf5YLws9gA=
+github.com/google/pprof v0.0.0-20230811205829-9131a7e9cc17/go.mod h1:Jh3hGz2jkYak8qXPD19ryItVnUgpgeqzdkY/D0EaeuA=
+github.com/ianlancetaylor/demangle v0.0.0-20230524184225-eabc099b10ab h1:BA4a7pe6ZTd9F8kXETBoijjFJ/ntaa//1wiH9BZu4zU=
+github.com/ianlancetaylor/demangle v0.0.0-20230524184225-eabc099b10ab/go.mod h1:gx7rwoVhcfuVKG5uya9Hs3Sxj7EIvldVofAWIUtGouw=
 golang.org/x/arch v0.4.0 h1:A8WCeEWhLwPBKNbFi5Wv5UTCBx5zzubnXDlMOFAzFMc=
 golang.org/x/arch v0.4.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
 golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc=
index 0c702398d3a23fe9768cdbbf6e4b672af24c76d2..c2e45c6a83e720115d39644c51364ef8776e1cba 100644 (file)
@@ -83,7 +83,7 @@ func (a *addr2LinerJob) close() {
        a.cmd.Wait()
 }
 
-// newAddr2liner starts the given addr2liner command reporting
+// newAddr2Liner starts the given addr2liner command reporting
 // information about the given executable file. If file is a shared
 // library, base should be the address at which it was mapped in the
 // program under consideration.
index 844c7a475d5174f803142f5d87a6449596d210bb..491422fcda140fa887ab005380ed57610d3f39ab 100644 (file)
@@ -66,7 +66,7 @@ func (a *llvmSymbolizerJob) close() {
        a.cmd.Wait()
 }
 
-// newLlvmSymbolizer starts the given llvmSymbolizer command reporting
+// newLLVMSymbolizer starts the given llvmSymbolizer command reporting
 // information about the given executable file. If file is a shared
 // library, base should be the address at which it was mapped in the
 // program under consideration.
index e64adf58cd6bd4e3698b0181f870894e40e2a66a..2709ef877c3c4549b5fc0e83e346ed6d40e5f290 100644 (file)
@@ -95,8 +95,8 @@ func matchSymbol(names []string, start, end uint64, r *regexp.Regexp, address ui
                // Match all possible demangled versions of the name.
                for _, o := range [][]demangle.Option{
                        {demangle.NoClones},
-                       {demangle.NoParams},
-                       {demangle.NoParams, demangle.NoTemplateParams},
+                       {demangle.NoParams, demangle.NoEnclosingParams},
+                       {demangle.NoParams, demangle.NoEnclosingParams, demangle.NoTemplateParams},
                } {
                        if demangled, err := demangle.ToString(name, o...); err == nil && r.MatchString(demangled) {
                                return []string{demangled}
index a9cae92d1bf8c59df3d689b479a563b1b0041388..b97ef8516934ae157ccdd068fa85e316ddd40b62 100644 (file)
@@ -18,7 +18,6 @@ import (
        "errors"
        "fmt"
        "os"
-       "strings"
 
        "github.com/google/pprof/internal/binutils"
        "github.com/google/pprof/internal/plugin"
@@ -67,7 +66,7 @@ func parseFlags(o *plugin.Options) (*source, []string, error) {
        flagTools := flag.String("tools", os.Getenv("PPROF_TOOLS"), "Path for object tool pathnames")
 
        flagHTTP := flag.String("http", "", "Present interactive web UI at the specified http host:port")
-       flagNoBrowser := flag.Bool("no_browser", false, "Skip opening a browswer for the interactive web UI")
+       flagNoBrowser := flag.Bool("no_browser", false, "Skip opening a browser for the interactive web UI")
 
        // Flags that set configuration properties.
        cfg := currentConfig()
@@ -102,9 +101,6 @@ func parseFlags(o *plugin.Options) (*source, []string, error) {
                        file.Close()
                        execName = arg0
                        args = args[1:]
-               } else if *flagBuildID == "" && isBuildID(arg0) {
-                       *flagBuildID = arg0
-                       args = args[1:]
                }
        }
 
@@ -265,12 +261,6 @@ func installConfigFlags(flag plugin.FlagSet, cfg *config) func() error {
        }
 }
 
-// isBuildID determines if the profile may contain a build ID, by
-// checking that it is a string of hex digits.
-func isBuildID(id string) bool {
-       return strings.Trim(id, "0123456789abcdefABCDEF") == ""
-}
-
 func sampleIndex(flag *bool, si string, sampleType, option string, ui plugin.UI) string {
        if *flag {
                if si == "" {
@@ -364,5 +354,7 @@ var usageMsgVars = "\n\n" +
        "   PPROF_BINARY_PATH  Search path for local binary files\n" +
        "                      default: $HOME/pprof/binaries\n" +
        "                      searches $buildid/$name, $buildid/*, $path/$buildid,\n" +
-       "                      ${buildid:0:2}/${buildid:2}.debug, $name, $path\n" +
+       "                      ${buildid:0:2}/${buildid:2}.debug, $name, $path,\n" +
+       "                      ${name}.debug, $dir/.debug/${name}.debug,\n" +
+       "                      usr/lib/debug/$dir/${name}.debug\n" +
        "   * On Windows, %USERPROFILE% is used instead of $HOME"
index 5ddee33610f2d0ab6886bd354512a39d8910d001..584c5d85e0e123c1c8dae0190054ca7bf15a78da 100644 (file)
@@ -389,7 +389,7 @@ func collectMappingSources(p *profile.Profile, source string) plugin.MappingSour
 // set to the remote source URL by collectMappingSources back to empty string.
 func unsourceMappings(p *profile.Profile) {
        for _, m := range p.Mapping {
-               if m.BuildID == "" {
+               if m.BuildID == "" && filepath.VolumeName(m.File) == "" {
                        if u, err := url.Parse(m.File); err == nil && u.IsAbs() {
                                m.File = ""
                        }
@@ -408,9 +408,13 @@ func locateBinaries(p *profile.Profile, s *source, obj plugin.ObjTool, ui plugin
        }
 mapping:
        for _, m := range p.Mapping {
+               var noVolumeFile string
                var baseName string
+               var dirName string
                if m.File != "" {
+                       noVolumeFile = strings.TrimPrefix(m.File, filepath.VolumeName(m.File))
                        baseName = filepath.Base(m.File)
+                       dirName = filepath.Dir(noVolumeFile)
                }
 
                for _, path := range filepath.SplitList(searchPath) {
@@ -420,7 +424,7 @@ mapping:
                                if matches, err := filepath.Glob(filepath.Join(path, m.BuildID, "*")); err == nil {
                                        fileNames = append(fileNames, matches...)
                                }
-                               fileNames = append(fileNames, filepath.Join(path, m.File, m.BuildID)) // perf path format
+                               fileNames = append(fileNames, filepath.Join(path, noVolumeFile, m.BuildID)) // perf path format
                                // Llvm buildid protocol: the first two characters of the build id
                                // are used as directory, and the remaining part is in the filename.
                                // e.g. `/ab/cdef0123456.debug`
@@ -429,10 +433,13 @@ mapping:
                        if m.File != "" {
                                // Try both the basename and the full path, to support the same directory
                                // structure as the perf symfs option.
-                               if baseName != "" {
-                                       fileNames = append(fileNames, filepath.Join(path, baseName))
-                               }
-                               fileNames = append(fileNames, filepath.Join(path, m.File))
+                               fileNames = append(fileNames, filepath.Join(path, baseName))
+                               fileNames = append(fileNames, filepath.Join(path, noVolumeFile))
+                               // Other locations: use the same search paths as GDB, according to
+                               // https://sourceware.org/gdb/onlinedocs/gdb/Separate-Debug-Files.html
+                               fileNames = append(fileNames, filepath.Join(path, noVolumeFile+".debug"))
+                               fileNames = append(fileNames, filepath.Join(path, dirName, ".debug", baseName+".debug"))
+                               fileNames = append(fileNames, filepath.Join(path, "usr", "lib", "debug", dirName, baseName+".debug"))
                        }
                        for _, name := range fileNames {
                                if f, err := obj.Open(name, m.Start, m.Limit, m.Offset, m.KernelRelocationSymbol); err == nil {
@@ -461,8 +468,8 @@ mapping:
                        l.Mapping = m
                }
        }
-       // Replace executable filename/buildID with the overrides from source.
-       // Assumes the executable is the first Mapping entry.
+       // If configured, apply executable filename override and (maybe, see below)
+       // build ID override from source. Assume the executable is the first mapping.
        if execName, buildID := s.ExecName, s.BuildID; execName != "" || buildID != "" {
                m := p.Mapping[0]
                if execName != "" {
@@ -470,7 +477,10 @@ mapping:
                        // the source override is most likely missing it.
                        m.File = execName
                }
-               if buildID != "" {
+               // Only apply the build ID override if the build ID in the main mapping is
+               // missing. Overwriting the build ID in case it's present is very likely a
+               // wrong thing to do so we refuse to do that.
+               if buildID != "" && m.BuildID == "" {
                        m.BuildID = buildID
                }
        }
index 5282c1b363f1f48e5aef21af966af5c26e012680..ff980f66de4254e157241beb070f4e2ceca6cd67 100644 (file)
@@ -563,11 +563,11 @@ function viewer(baseUrl, nodes, options) {
     return str.replace(/([\\\.?+*\[\](){}|^$])/g, '\\$1');
   }
 
-  function setSampleIndexLink(id) {
-    const elem = document.getElementById(id);
+  function setSampleIndexLink(si) {
+    const elem = document.getElementById('sampletype-' + si);
     if (elem != null) {
       setHrefParams(elem, function (params) {
-        params.set("si", id);
+        params.set("si", si);
       });
     }
   }
@@ -682,8 +682,10 @@ function viewer(baseUrl, nodes, options) {
     toptable.addEventListener('touchstart', handleTopClick);
   }
 
-  const ids = ['topbtn', 'graphbtn', 'flamegraph', 'flamegraph2', 'peek', 'list',
-              'disasm', 'focus', 'ignore', 'hide', 'show', 'show-from'];
+  const ids = ['topbtn', 'graphbtn',
+               'flamegraph', 'flamegraph2', 'flamegraphold',
+               'peek', 'list',
+               'disasm', 'focus', 'ignore', 'hide', 'show', 'show-from'];
   ids.forEach(makeSearchLinkDynamic);
 
   const sampleIDs = [{{range .SampleTypes}}'{{.}}', {{end}}];
index 39cb55a1d14ad40d849651fa695215ef3dda3d60..42cb7960e6325d6d313ab276ea89c6579ffae487 100644 (file)
@@ -12,7 +12,7 @@
       <a title="{{.Help.top}}"  href="./top" id="topbtn">Top</a>
       <a title="{{.Help.graph}}" href="./" id="graphbtn">Graph</a>
       <a title="{{.Help.flamegraph}}" href="./flamegraph" id="flamegraph">Flame Graph</a>
-      <a title="{{.Help.flamegraph2}}" href="./flamegraph2" id="flamegraph2">Flame Graph (new)</a>
+      <a title="{{.Help.flamegraphold}}" href="./flamegraphold" id="flamegraphold">Flame Graph (old)</a>
       <a title="{{.Help.peek}}" href="./peek" id="peek">Peek</a>
       <a title="{{.Help.list}}" href="./source" id="list">Source</a>
       <a title="{{.Help.disasm}}" href="./disasm" id="disasm">Disassemble</a>
@@ -28,7 +28,7 @@
     </div>
     <div class="submenu">
       {{range .SampleTypes}}
-      <a href="?si={{.}}" id="{{.}}">{{.}}</a>
+      <a href="?si={{.}}" id="sampletype-{{.}}">{{.}}</a>
       {{end}}
     </div>
   </div>
index d142aa789cfcba24cca0b01cfcddf6468562bcf1..f5aeb9857a6d54f46bd90d129c0f49e4a989df6a 100644 (file)
@@ -28,7 +28,10 @@ body {
   position: absolute;
   overflow: hidden;
   box-sizing: border-box;
+  background: #d8d8d8;
 }
+.positive { position: absolute; background: #caa; }
+.negative { position: absolute; background: #aca; }
 /* Not-inlined frames are visually separated from their caller. */
 .not-inlined {
   border-top: 1px solid black;
@@ -47,11 +50,6 @@ body {
 /* Box highlighting via shadows to avoid size changes */
 .hilite { box-shadow: 0px 0px 0px 2px #000; z-index: 1; }
 .hilite2 { box-shadow: 0px 0px 0px 2px #000; z-index: 1; }
-/* Self-cost region inside a box */
-.self {
-  position: absolute;
-  background: rgba(0,0,0,0.25); /* Darker hue */
-}
 /* Gap left between callers and callees */
 .separator {
   position: absolute;
index 64229a000a01bfc3fed0b0ccb65f4209e80f95e5..be78edd553f7456aff9c8ef2c4acfad28f68e155 100644 (file)
@@ -31,13 +31,20 @@ function stackViewer(stacks, nodes) {
       ['hrs', 60*60]]]]);
 
   // Fields
-  let shownTotal = 0;       // Total value of all stacks
   let pivots = [];          // Indices of currently selected data.Sources entries.
   let matches = new Set();  // Indices of sources that match search
   let elems = new Map();    // Mapping from source index to display elements
   let displayList = [];     // List of boxes to display.
   let actionMenuOn = false; // Is action menu visible?
   let actionTarget = null;  // Box on which action menu is operating.
+  let diff = false;         // Are we displaying a diff?
+
+  for (const stack of stacks.Stacks) {
+    if (stack.Value < 0) {
+      diff = true;
+      break;
+    }
+  }
 
   // Setup to allow measuring text width.
   const textSizer = document.createElement('canvas');
@@ -177,9 +184,8 @@ function stackViewer(stacks, nodes) {
   function handleEnter(box, div) {
     if (actionMenuOn) return;
     const src = stacks.Sources[box.src];
-    const d = details(box);
-    div.title = d + ' ' + src.FullName + (src.Inlined ? "\n(inlined)" : "");
-    detailBox.innerText = d;
+    div.title = details(box) + ' â”‚ ' + src.FullName + (src.Inlined ? "\n(inlined)" : "");
+    detailBox.innerText = summary(box.sumpos, box.sumneg);
     // Highlight all boxes that have the same source as box.
     toggleClass(box.src, 'hilite2', true);
   }
@@ -228,16 +234,16 @@ function stackViewer(stacks, nodes) {
     const width = chart.clientWidth;
     elems.clear();
     actionTarget = null;
-    const total = totalValue(places);
+    const [pos, neg] = totalValue(places);
+    const total = pos + neg;
     const xscale = (width-2*PADDING) / total; // Converts from profile value to X pixels
     const x = PADDING;
     const y = 0;
-    shownTotal = total;
 
     displayList.length = 0;
     renderStacks(0, xscale, x, y, places, +1);  // Callees
     renderStacks(0, xscale, x, y-ROW, places, -1);  // Callers (ROW left for separator)
-    display(displayList);
+    display(xscale, pos, neg, displayList);
   }
 
   // renderStacks creates boxes with top-left at x,y with children drawn as
@@ -256,29 +262,59 @@ function stackViewer(stacks, nodes) {
     const groups = partitionPlaces(places);
     for (const g of groups) {
       renderGroup(depth, xscale, x, y, g, direction);
-      x += xscale*g.sum;
+      x += groupWidth(xscale, g);
     }
   }
 
+  // Some of the types used below:
+  //
+  // // Group represents a displayed (sub)tree.
+  // interface Group {
+  //   name: string;     // Full name of source
+  //   src: number;     // Index in stacks.Sources
+  //   self: number;     // Contribution as leaf (may be < 0 for diffs)
+  //   sumpos: number;  // Sum of |self| of positive nodes in tree (>= 0)
+  //   sumneg: number;  // Sum of |self| of negative nodes in tree (>= 0)
+  //   places: Place[];  // Stack slots that contributed to this group
+  // }
+  //
+  // // Box is a rendered item.
+  // interface Box {
+  //   x: number;         // X coordinate of top-left
+  //   y: number;         // Y coordinate of top-left
+  //   width: number;     // Width of box to display
+  //   src: number;       // Index in stacks.Sources
+  //   sumpos: number;    // From corresponding Group
+  //   sumneg: number;    // From corresponding Group
+  //   self: number;      // From corresponding Group
+  // };
+
+  function groupWidth(xscale, g) {
+    return xscale * (g.sumpos + g.sumneg);
+  }
+
   function renderGroup(depth, xscale, x, y, g, direction) {
     // Skip if not wide enough.
-    const width = xscale * g.sum;
+    const width = groupWidth(xscale, g);
     if (width < MIN_WIDTH) return;
 
     // Draw the box for g.src (except for selected element in upwards direction
     // since that duplicates the box we added in downwards direction).
     if (depth != 0 || direction > 0) {
       const box = {
-        x:         x,
-        y:         y,
-        src:       g.src,
-        sum:       g.sum,
-        selfValue: g.self,
-        width:     xscale*g.sum,
-        selfWidth: (direction > 0) ? xscale*g.self : 0,
+        x:      x,
+        y:      y,
+        width:  width,
+        src:    g.src,
+       sumpos: g.sumpos,
+       sumneg: g.sumneg,
+        self:   g.self,
       };
       displayList.push(box);
-      x += box.selfWidth;
+      if (direction > 0) {
+       // Leave gap on left hand side to indicate self contribution.
+       x += xscale*Math.abs(g.self);
+      }
     }
     y += direction * ROW;
 
@@ -322,11 +358,15 @@ function stackViewer(stacks, nodes) {
       let group = groupMap.get(src);
       if (!group) {
         const name = stacks.Sources[src].FullName;
-        group = {name: name, src: src, sum: 0, self: 0, places: []};
+        group = {name: name, src: src, sumpos: 0, sumneg: 0, self: 0, places: []};
         groupMap.set(src, group);
         groups.push(group);
       }
-      group.sum += stack.Value;
+      if (stack.Value < 0) {
+       group.sumneg += -stack.Value;
+      } else {
+       group.sumpos += stack.Value;
+      }
       group.self += (place.Pos == stack.Sources.length-1) ? stack.Value : 0;
       group.places.push(place);
     }
@@ -334,12 +374,14 @@ function stackViewer(stacks, nodes) {
     // Order by decreasing cost (makes it easier to spot heavy functions).
     // Though alphabetical ordering is a potential alternative that will make
     // profile comparisons easier.
-    groups.sort(function(a, b) { return b.sum - a.sum; });
+    groups.sort(function(a, b) {
+      return (b.sumpos + b.sumneg) - (a.sumpos + a.sumneg);
+    });
 
     return groups;
   }
 
-  function display(list) {
+  function display(xscale, posTotal, negTotal, list) {
     // Sort boxes so that text selection follows a predictable order.
     list.sort(function(a, b) {
       if (a.y != b.y) return a.y - b.y;
@@ -353,40 +395,46 @@ function stackViewer(stacks, nodes) {
     const divs = [];
     for (const box of list) {
       box.y -= adjust;
-      divs.push(drawBox(box));
+      divs.push(drawBox(xscale, box));
     }
-    divs.push(drawSep(-adjust));
+    divs.push(drawSep(-adjust, posTotal, negTotal));
 
     const h = (list.length > 0 ?  list[list.length-1].y : 0) + 4*ROW;
     chart.style.height = h+'px';
     chart.replaceChildren(...divs);
   }
 
-  function drawBox(box) {
+  function drawBox(xscale, box) {
     const srcIndex = box.src;
     const src = stacks.Sources[srcIndex];
 
+    function makeRect(cl, x, y, w, h) {
+      const r = document.createElement('div');
+      r.style.left = x+'px';
+      r.style.top = y+'px';
+      r.style.width = w+'px';
+      r.style.height = h+'px';
+      r.classList.add(cl);
+      return r;
+    }
+
     // Background
     const w = box.width - 1; // Leave 1px gap
-    const r = document.createElement('div');
-    r.style.left = box.x + 'px';
-    r.style.top = box.y + 'px';
-    r.style.width = w + 'px';
-    r.style.height = ROW + 'px';
-    r.classList.add('boxbg');
-    r.style.background = makeColor(src.Color);
+    const r = makeRect('boxbg', box.x, box.y, w, ROW);
+    if (!diff) r.style.background = makeColor(src.Color);
     addElem(srcIndex, r);
     if (!src.Inlined) {
       r.classList.add('not-inlined');
     }
 
-    // Box that shows time spent in self
-    if (box.selfWidth >= MIN_WIDTH) {
-      const s = document.createElement('div');
-      s.style.width = Math.min(box.selfWidth, w)+'px';
-      s.style.height = (ROW-1)+'px';
-      s.classList.add('self');
-      r.appendChild(s);
+    // Positive/negative indicator for diff mode.
+    if (diff) {
+      const delta = box.sumpos - box.sumneg;
+      const partWidth = xscale * Math.abs(delta);
+      if (partWidth >= MIN_WIDTH) {
+       r.appendChild(makeRect((delta < 0 ? 'negative' : 'positive'),
+                              0, 0, partWidth, ROW-1));
+      }
     }
 
     // Label
@@ -404,11 +452,9 @@ function stackViewer(stacks, nodes) {
     return r;
   }
 
-  function drawSep(y) {
+  function drawSep(y, posTotal, negTotal) {
     const m = document.createElement('div');
-    m.innerText = percent(shownTotal, stacks.Total) +
-       '\xa0\xa0\xa0\xa0' +  // Some non-breaking spaces
-       valueString(shownTotal);
+    m.innerText = summary(posTotal, negTotal);
     m.style.top = (y-ROW) + 'px';
     m.style.left = PADDING + 'px';
     m.style.width = (chart.clientWidth - PADDING*2) + 'px';
@@ -458,36 +504,66 @@ function stackViewer(stacks, nodes) {
     t.innerText = text;
   }
 
-  // totalValue returns the combined sum of the stacks listed in places.
+  // totalValue returns the positive and negative sums of the Values of stacks
+  // listed in places.
   function totalValue(places) {
     const seen = new Set();
-    let result = 0;
+    let pos = 0;
+    let neg = 0;
     for (const place of places) {
       if (seen.has(place.Stack)) continue; // Do not double-count stacks
       seen.add(place.Stack);
       const stack = stacks.Stacks[place.Stack];
-      result += stack.Value;
+      if (stack.Value < 0) {
+       neg += -stack.Value;
+      } else {
+       pos += stack.Value;
+      }
     }
-    return result;
+    return [pos, neg];
+  }
+
+  function summary(pos, neg) {
+    // Examples:
+    //    6s (10%)
+    //    12s (20%) ðŸ † 18s (30%)
+    return diff ? diffText(neg, pos) : percentText(pos);
   }
 
   function details(box) {
-    // E.g., 10% 7s
-    // or    10% 7s (3s self
-    let result = percent(box.sum, stacks.Total) + ' ' + valueString(box.sum);
-    if (box.selfValue > 0) {
-      result += ` (${valueString(box.selfValue)} self)`;
+    // Examples:
+    //    6s (10%)
+    //    6s (10%) â”‚ self 3s (5%)
+    //    6s (10%) â”‚ 12s (20%) ðŸ † 18s (30%)
+    let result = percentText(box.sumpos - box.sumneg);
+    if (box.self != 0) {
+      result += " â”‚ self " + unitText(box.self);
+    }
+    if (diff && box.sumpos > 0 && box.sumneg > 0) {
+      result += " â”‚ " + diffText(box.sumneg, box.sumpos);
     }
     return result;
   }
 
-  function percent(v, total) {
-    return Number(((100.0 * v) / total).toFixed(1)) + '%';
+  // diffText returns text that displays from and to alongside their percentages.
+  // E.g., 9s (45%) ðŸ † 10s (50%)
+  function diffText(from, to) {
+    return percentText(from) + " ðŸ † " + percentText(to);
+  }
+
+  // percentText returns text that displays v in appropriate units alongside its
+  // percentange.
+  function percentText(v) {
+    function percent(v, total) {
+      return Number(((100.0 * v) / total).toFixed(1)) + '%';
+    }
+    return unitText(v) + " (" + percent(v, stacks.Total) + ")";
   }
 
-  // valueString returns a formatted string to display for value.
-  function valueString(value) {
-    let v = value * stacks.Scale;
+  // unitText returns a formatted string to display for value.
+  function unitText(value) {
+    const sign = (value < 0) ? "-" : "";
+    let v = Math.abs(value) * stacks.Scale;
     // Rescale to appropriate display unit.
     let unit = stacks.Unit;
     const list = UNITS.get(unit);
@@ -501,7 +577,7 @@ function stackViewer(stacks, nodes) {
         }
       }
     }
-    return Number(v.toFixed(2)) + unit;
+    return sign + Number(v.toFixed(2)) + unit;
   }
 
   function find(name) {
index 8881e39eb285016ce95324edb5a5c7521487ee09..41b30021f5c5651aadbc21477ef542e8a33943b3 100644 (file)
@@ -112,7 +112,7 @@ func serveWebInterface(hostport string, p *profile.Profile, o *plugin.Options, d
        ui.help["details"] = "Show information about the profile and this view"
        ui.help["graph"] = "Display profile as a directed graph"
        ui.help["flamegraph"] = "Display profile as a flame graph"
-       ui.help["flamegraph2"] = "Display profile as a flame graph (experimental version that can display caller info on selection)"
+       ui.help["flamegraphold"] = "Display profile as a flame graph (old version; slated for removal)"
        ui.help["reset"] = "Show the entire profile"
        ui.help["save_config"] = "Save current settings"
 
@@ -125,15 +125,16 @@ func serveWebInterface(hostport string, p *profile.Profile, o *plugin.Options, d
                Host:     host,
                Port:     port,
                Handlers: map[string]http.Handler{
-                       "/":             http.HandlerFunc(ui.dot),
-                       "/top":          http.HandlerFunc(ui.top),
-                       "/disasm":       http.HandlerFunc(ui.disasm),
-                       "/source":       http.HandlerFunc(ui.source),
-                       "/peek":         http.HandlerFunc(ui.peek),
-                       "/flamegraph":   http.HandlerFunc(ui.flamegraph),
-                       "/flamegraph2":  http.HandlerFunc(ui.stackView), // Experimental
-                       "/saveconfig":   http.HandlerFunc(ui.saveConfig),
-                       "/deleteconfig": http.HandlerFunc(ui.deleteConfig),
+                       "/":              http.HandlerFunc(ui.dot),
+                       "/top":           http.HandlerFunc(ui.top),
+                       "/disasm":        http.HandlerFunc(ui.disasm),
+                       "/source":        http.HandlerFunc(ui.source),
+                       "/peek":          http.HandlerFunc(ui.peek),
+                       "/flamegraphold": http.HandlerFunc(ui.flamegraph),
+                       "/flamegraph":    http.HandlerFunc(ui.stackView),
+                       "/flamegraph2":   http.HandlerFunc(ui.stackView), // Support older URL
+                       "/saveconfig":    http.HandlerFunc(ui.saveConfig),
+                       "/deleteconfig":  http.HandlerFunc(ui.deleteConfig),
                        "/download": http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
                                w.Header().Set("Content-Type", "application/vnd.google.protobuf+gzip")
                                w.Header().Set("Content-Disposition", "attachment;filename=profile.pb.gz")
index 74b904c402e61eadd48f96a2345b7aeeb15fd556..b64ef27991a4ca1d364faa036d77e77c197fc929 100644 (file)
@@ -33,7 +33,7 @@ var (
        javaRegExp = regexp.MustCompile(`^(?:[a-z]\w*\.)*([A-Z][\w\$]*\.(?:<init>|[a-z][\w\$]*(?:\$\d+)?))(?:(?:\()|$)`)
        // Removes package name and method arguments for Go function names.
        // See tests for examples.
-       goRegExp = regexp.MustCompile(`^(?:[\w\-\.]+\/)+(.+)`)
+       goRegExp = regexp.MustCompile(`^(?:[\w\-\.]+\/)+([^.]+\..+)`)
        // Removes potential module versions in a package path.
        goVerRegExp = regexp.MustCompile(`^(.*?)/v(?:[2-9]|[1-9][0-9]+)([./].*)$`)
        // Strips C++ namespace prefix from a C++ function / method name.
index b5fcfbc3e463f98b1e33f1e7232b4ea9ccb539f3..d9644f9326c49d5d962b40c5163c04d3e9789105 100644 (file)
@@ -121,7 +121,7 @@ func compatibleValueTypes(v1, v2 *profile.ValueType) bool {
        return false
 }
 
-// Scale a measurement from an unit to a different unit and returns
+// Scale a measurement from a unit to a different unit and returns
 // the scaled value and the target unit. The returned target unit
 // will be empty if uninteresting (could be skipped).
 func Scale(value int64, fromUnit, toUnit string) (float64, string) {
index 6d538599b04c52cf085579193c338c8af810df61..0f6dcf53508910fe41c2e3844a5f70c5dced777f 100644 (file)
@@ -4,7 +4,7 @@ import "regexp"
 
 // pkgRE extracts package name, It looks for the first "." or "::" that occurs
 // after the last "/". (Searching after the last / allows us to correctly handle
-// names that look like "some.url.com/foo.bar".
+// names that look like "some.url.com/foo.bar".)
 var pkgRE = regexp.MustCompile(`^((.*/)?[\w\d_]+)(\.|::)([^/]*)$`)
 
 // packageName returns the package name of the named symbol, or "" if not found.
index 36ddf2e934efd622d632289c97bb413fec186fdf..f73e49a176c5c7d495c30ff742a97ff52d75f92f 100644 (file)
@@ -433,7 +433,16 @@ func PrintAssembly(w io.Writer, rpt *Report, obj plugin.ObjTool, maxFuncs int) e
        }
 
        if len(syms) == 0 {
-               return fmt.Errorf("no matches found for regexp: %s", o.Symbol)
+               // The symbol regexp case
+               if address == nil {
+                       return fmt.Errorf("no matches found for regexp %s", o.Symbol)
+               }
+
+               // The address case
+               if len(symbols) == 0 {
+                       return fmt.Errorf("no matches found for address 0x%x", *address)
+               }
+               return fmt.Errorf("address 0x%x found in binary, but the corresponding symbols do not have samples in the profile", *address)
        }
 
        // Correlate the symbols from the binary with the profile samples.
@@ -505,22 +514,26 @@ func PrintAssembly(w io.Writer, rpt *Report, obj plugin.ObjTool, maxFuncs int) e
        return nil
 }
 
-// symbolsFromBinaries examines the binaries listed on the profile
-// that have associated samples, and identifies symbols matching rx.
+// symbolsFromBinaries examines the binaries listed on the profile that have
+// associated samples, and returns the identified symbols matching rx.
 func symbolsFromBinaries(prof *profile.Profile, g *graph.Graph, rx *regexp.Regexp, address *uint64, obj plugin.ObjTool) []*objSymbol {
-       hasSamples := make(map[string]bool)
-       // Only examine mappings that have samples that match the
-       // regexp. This is an optimization to speed up pprof.
+       // fileHasSamplesAndMatched is for optimization to speed up pprof: when later
+       // walking through the profile mappings, it will only examine the ones that have
+       // samples and are matched to the regexp.
+       fileHasSamplesAndMatched := make(map[string]bool)
        for _, n := range g.Nodes {
                if name := n.Info.PrintableName(); rx.MatchString(name) && n.Info.Objfile != "" {
-                       hasSamples[n.Info.Objfile] = true
+                       fileHasSamplesAndMatched[n.Info.Objfile] = true
                }
        }
 
        // Walk all mappings looking for matching functions with samples.
        var objSyms []*objSymbol
        for _, m := range prof.Mapping {
-               if !hasSamples[m.File] {
+               // Skip the mapping if its file does not have samples or is not matched to
+               // the regexp (unless the regexp is an address and the mapping's range covers
+               // the address)
+               if !fileHasSamplesAndMatched[m.File] {
                        if address == nil || !(m.Start <= *address && *address <= m.Limit) {
                                continue
                        }
index 87f202bdd479e6ff6e0f4cddc41d290b8c38957a..c3f6cc62815599027bf2b65b5a608a3a61066b87 100644 (file)
@@ -214,9 +214,9 @@ func Demangle(prof *profile.Profile, force bool, demanglerMode string) {
 func demanglerModeToOptions(demanglerMode string) []demangle.Option {
        switch demanglerMode {
        case "": // demangled, simplified: no parameters, no templates, no return type
-               return []demangle.Option{demangle.NoParams, demangle.NoTemplateParams}
+               return []demangle.Option{demangle.NoParams, demangle.NoEnclosingParams, demangle.NoTemplateParams}
        case "templates": // demangled, simplified: no parameters, no return type
-               return []demangle.Option{demangle.NoParams}
+               return []demangle.Option{demangle.NoParams, demangle.NoEnclosingParams}
        case "full":
                return []demangle.Option{demangle.NoClones}
        case "none": // no demangling
@@ -371,7 +371,7 @@ type mappingTable struct {
        segments map[*profile.Mapping]plugin.ObjFile
 }
 
-// Close releases any external processes being used for the mapping.
+// close releases any external processes being used for the mapping.
 func (mt *mappingTable) close() {
        for _, segment := range mt.segments {
                segment.Close()
index c8a1beb8a8cb19bdc21b3b244a8f45310a32dd26..182c926b9089842a6f1c4e2cc84d8b371e3ebde3 100644 (file)
@@ -258,10 +258,10 @@ func (p *Profile) postDecode() error {
                // If this a main linux kernel mapping with a relocation symbol suffix
                // ("[kernel.kallsyms]_text"), extract said suffix.
                // It is fairly hacky to handle at this level, but the alternatives appear even worse.
-               if strings.HasPrefix(m.File, "[kernel.kallsyms]") {
-                       m.KernelRelocationSymbol = strings.ReplaceAll(m.File, "[kernel.kallsyms]", "")
+               const prefix = "[kernel.kallsyms]"
+               if strings.HasPrefix(m.File, prefix) {
+                       m.KernelRelocationSymbol = m.File[len(prefix):]
                }
-
        }
 
        functions := make(map[uint64]*Function, len(p.Function))
index 4ec00fe7d98fe3484099c20239ef9dda9bb4d716..60ef7e92687f56e20c9b0541cf555f0c3caadc15 100644 (file)
@@ -72,9 +72,23 @@ type ValueType struct {
 type Sample struct {
        Location []*Location
        Value    []int64
-       Label    map[string][]string
+       // Label is a per-label-key map to values for string labels.
+       //
+       // In general, having multiple values for the given label key is strongly
+       // discouraged - see docs for the sample label field in profile.proto.  The
+       // main reason this unlikely state is tracked here is to make the
+       // decoding->encoding roundtrip not lossy. But we expect that the value
+       // slices present in this map are always of length 1.
+       Label map[string][]string
+       // NumLabel is a per-label-key map to values for numeric labels. See a note
+       // above on handling multiple values for a label.
        NumLabel map[string][]int64
-       NumUnit  map[string][]string
+       // NumUnit is a per-label-key map to the unit names of corresponding numeric
+       // label values. The unit info may be missing even if the label is in
+       // NumLabel, see the docs in profile.proto for details. When the value is
+       // slice is present and not nil, its length must be equal to the length of
+       // the corresponding value slice in NumLabel.
+       NumUnit map[string][]string
 
        locationIDX []uint64
        labelX      []label
@@ -715,6 +729,35 @@ func (s *Sample) HasLabel(key, value string) bool {
        return false
 }
 
+// SetNumLabel sets the specified key to the specified value for all samples in the
+// profile. "unit" is a slice that describes the units that each corresponding member
+// of "values" is measured in (e.g. bytes or seconds).  If there is no relevant
+// unit for a given value, that member of "unit" should be the empty string.
+// "unit" must either have the same length as "value", or be nil.
+func (p *Profile) SetNumLabel(key string, value []int64, unit []string) {
+       for _, sample := range p.Sample {
+               if sample.NumLabel == nil {
+                       sample.NumLabel = map[string][]int64{key: value}
+               } else {
+                       sample.NumLabel[key] = value
+               }
+               if sample.NumUnit == nil {
+                       sample.NumUnit = map[string][]string{key: unit}
+               } else {
+                       sample.NumUnit[key] = unit
+               }
+       }
+}
+
+// RemoveNumLabel removes all numerical labels associated with the specified key for all
+// samples in the profile.
+func (p *Profile) RemoveNumLabel(key string) {
+       for _, sample := range p.Sample {
+               delete(sample.NumLabel, key)
+               delete(sample.NumUnit, key)
+       }
+}
+
 // DiffBaseSample returns true if a sample belongs to the diff base and false
 // otherwise.
 func (s *Sample) DiffBaseSample() bool {
diff --git a/src/cmd/vendor/github.com/ianlancetaylor/demangle/SECURITY.md b/src/cmd/vendor/github.com/ianlancetaylor/demangle/SECURITY.md
new file mode 100644 (file)
index 0000000..f4edf9e
--- /dev/null
@@ -0,0 +1,13 @@
+# Security Policy
+
+## Supported Versions
+
+Security updates are applied only to the latest release.
+
+## Reporting a Vulnerability
+
+If you have discovered a security vulnerability in this project, please report it privately. **Do not disclose it as a public issue.** This gives us time to work with you to fix the issue before public exposure, reducing the chance that the exploit will be used before a patch is released.
+
+Please disclose it at [security advisory](https://github.com/ianlancetaylor/demangle/security/advisories/new).
+
+This project is maintained by volunteers on a reasonable-effort basis. As such, please give us at least 90 days to work on a fix before public exposure.
index 20d8a9982e27d8142d2e604e0f9a5bfecf3155ea..cdc98c3368b50e7b1c751a4f1a03cbe5bbc56ad5 100644 (file)
@@ -38,25 +38,42 @@ type AST interface {
 // ASTToString returns the demangled name of the AST.
 func ASTToString(a AST, options ...Option) string {
        tparams := true
+       enclosingParams := true
        llvmStyle := false
+       max := 0
        for _, o := range options {
-               switch {
-               case NoTemplateParams:
+               switch {
+               case o == NoTemplateParams:
                        tparams = false
-               case LLVMStyle:
+               case o == NoEnclosingParams:
+                       enclosingParams = false
+               case o == LLVMStyle:
                        llvmStyle = true
+               case isMaxLength(o):
+                       max = maxLength(o)
                }
        }
 
-       ps := printState{tparams: tparams, llvmStyle: llvmStyle}
+       ps := printState{
+               tparams:         tparams,
+               enclosingParams: enclosingParams,
+               llvmStyle:       llvmStyle,
+               max:             max,
+       }
        a.print(&ps)
-       return ps.buf.String()
+       s := ps.buf.String()
+       if max > 0 && len(s) > max {
+               s = s[:max]
+       }
+       return s
 }
 
 // The printState type holds information needed to print an AST.
 type printState struct {
-       tparams   bool // whether to print template parameters
-       llvmStyle bool
+       tparams         bool // whether to print template parameters
+       enclosingParams bool // whether to print enclosing parameters
+       llvmStyle       bool
+       max             int // maximum output length
 
        buf  strings.Builder
        last byte // Last byte written to buffer.
@@ -88,6 +105,10 @@ func (ps *printState) writeString(s string) {
 
 // Print an AST.
 func (ps *printState) print(a AST) {
+       if ps.max > 0 && ps.buf.Len() > ps.max {
+               return
+       }
+
        c := 0
        for _, v := range ps.printing {
                if v == a {
@@ -1144,7 +1165,7 @@ type FunctionType struct {
 
 func (ft *FunctionType) print(ps *printState) {
        retType := ft.Return
-       if ft.ForLocalName && !ps.llvmStyle {
+       if ft.ForLocalName && (!ps.enclosingParams || !ps.llvmStyle) {
                retType = nil
        }
        if retType != nil {
@@ -1201,16 +1222,18 @@ func (ft *FunctionType) printArgs(ps *printState) {
        }
 
        ps.writeByte('(')
-       first := true
-       for _, a := range ft.Args {
-               if ps.isEmpty(a) {
-                       continue
-               }
-               if !first {
-                       ps.writeString(", ")
+       if !ft.ForLocalName || ps.enclosingParams {
+               first := true
+               for _, a := range ft.Args {
+                       if ps.isEmpty(a) {
+                               continue
+                       }
+                       if !first {
+                               ps.writeString(", ")
+                       }
+                       ps.print(a)
+                       first = false
                }
-               ps.print(a)
-               first = false
        }
        ps.writeByte(')')
 
index 66ac7dde62a553f70519d8cd3141784f5bb1c9d6..14e77a6ac452be651074e6f8812f8022abe2782c 100644 (file)
@@ -27,11 +27,21 @@ type Option int
 
 const (
        // The NoParams option disables demangling of function parameters.
+       // It only omits the parameters of the function name being demangled,
+       // not the parameter types of other functions that may be mentioned.
+       // Using the option will speed up the demangler and cause it to
+       // use less memory.
        NoParams Option = iota
 
        // The NoTemplateParams option disables demangling of template parameters.
+       // This applies to both C++ and Rust.
        NoTemplateParams
 
+       // The NoEnclosingParams option disables demangling of the function
+       // parameter types of the enclosing function when demangling a
+       // local name defined within a function.
+       NoEnclosingParams
+
        // The NoClones option disables inclusion of clone suffixes.
        // NoParams implies NoClones.
        NoClones
@@ -51,6 +61,34 @@ const (
        LLVMStyle
 )
 
+// maxLengthShift is how we shift the MaxLength value.
+const maxLengthShift = 16
+
+// maxLengthMask is a mask for the maxLength value.
+const maxLengthMask = 0x1f << maxLengthShift
+
+// MaxLength returns an Option that limits the maximum length of a
+// demangled string. The maximum length is expressed as a power of 2,
+// so a value of 1 limits the returned string to 2 characters, and
+// a value of 16 limits the returned string to 65,536 characters.
+// The value must be between 1 and 30.
+func MaxLength(pow int) Option {
+       if pow <= 0 || pow > 30 {
+               panic("demangle: invalid MaxLength value")
+       }
+       return Option(pow << maxLengthShift)
+}
+
+// isMaxLength reports whether an Option holds a maximum length.
+func isMaxLength(opt Option) bool {
+       return opt&maxLengthMask != 0
+}
+
+// maxLength returns the maximum length stored in an Option.
+func maxLength(opt Option) int {
+       return 1 << ((opt & maxLengthMask) >> maxLengthShift)
+}
+
 // Filter demangles a C++ or Rust symbol name,
 // returning the human-readable C++ or Rust name.
 // If any error occurs during demangling, the input string is returned.
@@ -216,17 +254,19 @@ func doDemangle(name string, options ...Option) (ret AST, err error) {
        clones := true
        verbose := false
        for _, o := range options {
-               switch {
-               case NoParams:
+               switch {
+               case o == NoParams:
                        params = false
                        clones = false
-               case NoClones:
+               case o == NoClones:
                        clones = false
-               case Verbose:
+               case o == Verbose:
                        verbose = true
-               case NoTemplateParams, LLVMStyle:
+               case o == NoTemplateParams || o == NoEnclosingParams || o == LLVMStyle || isMaxLength(o):
                        // These are valid options but only affect
                        // printing of the AST.
+               case o == NoRust:
+                       // Unimportant here.
                default:
                        return nil, fmt.Errorf("unrecognized demangler option %v", o)
                }
@@ -660,7 +700,7 @@ func (st *state) prefix() AST {
                }
        }
 
-       isCast := false
+       var cast *Cast
        for {
                if len(st.str) == 0 {
                        st.fail("expected prefix")
@@ -672,7 +712,10 @@ func (st *state) prefix() AST {
                        un, isUnCast := st.unqualifiedName()
                        next = un
                        if isUnCast {
-                               isCast = true
+                               if tn, ok := un.(*TaggedName); ok {
+                                       un = tn.Name
+                               }
+                               cast = un.(*Cast)
                        }
                } else {
                        switch st.str[0] {
@@ -726,10 +769,10 @@ func (st *state) prefix() AST {
                                var args []AST
                                args = st.templateArgs()
                                tmpl := &Template{Name: a, Args: args}
-                               if isCast {
-                                       st.setTemplate(a, tmpl)
+                               if cast != nil {
+                                       st.setTemplate(cast, tmpl)
                                        st.clearTemplateArgs(args)
-                                       isCast = false
+                                       cast = nil
                                }
                                a = nil
                                next = tmpl
@@ -739,8 +782,12 @@ func (st *state) prefix() AST {
                                if a == nil {
                                        st.fail("expected prefix")
                                }
-                               if isCast {
-                                       st.setTemplate(a, nil)
+                               if cast != nil {
+                                       var toTmpl *Template
+                                       if castTempl, ok := cast.To.(*Template); ok {
+                                               toTmpl = castTempl
+                                       }
+                                       st.setTemplate(cast, toTmpl)
                                }
                                return a
                        case 'M':
@@ -770,10 +817,10 @@ func (st *state) prefix() AST {
                                }
                                st.advance(1)
                                tmpl := &Template{Name: a, Args: args}
-                               if isCast {
-                                       st.setTemplate(a, tmpl)
+                               if cast != nil {
+                                       st.setTemplate(cast, tmpl)
                                        st.clearTemplateArgs(args)
-                                       isCast = false
+                                       cast = nil
                                }
                                a = nil
                                next = tmpl
@@ -1715,7 +1762,7 @@ func (st *state) demangleCastTemplateArgs(tp AST, addSubst bool) AST {
        return tp
 }
 
-// mergeQualifiers merges two qualifer lists into one.
+// mergeQualifiers merges two qualifier lists into one.
 func mergeQualifiers(q1AST, q2AST AST) AST {
        if q1AST == nil {
                return q2AST
index 39792189acc5c341541b7bd2c4fe1173a08683df..f3d2d33bd0b5298071c9e1a90019e36a6439b4bd 100644 (file)
@@ -40,6 +40,15 @@ func rustToString(name string, options []Option) (ret string, err error) {
 
        name = name[2:]
        rst := &rustState{orig: name, str: name}
+
+       for _, o := range options {
+               if o == NoTemplateParams {
+                       rst.noGenericArgs = true
+               } else if isMaxLength(o) {
+                       rst.max = maxLength(o)
+               }
+       }
+
        rst.symbolName()
 
        if len(rst.str) > 0 {
@@ -62,18 +71,24 @@ func rustToString(name string, options []Option) (ret string, err error) {
                }
        }
 
-       return rst.buf.String(), nil
+       s := rst.buf.String()
+       if rst.max > 0 && len(s) > rst.max {
+               s = s[:rst.max]
+       }
+       return s, nil
 }
 
 // A rustState holds the current state of demangling a Rust string.
 type rustState struct {
-       orig      string          // the original string being demangled
-       str       string          // remainder of string to demangle
-       off       int             // offset of str within original string
-       buf       strings.Builder // demangled string being built
-       skip      bool            // don't print, just skip
-       lifetimes int64           // number of bound lifetimes
-       last      byte            // last byte written to buffer
+       orig          string          // the original string being demangled
+       str           string          // remainder of string to demangle
+       off           int             // offset of str within original string
+       buf           strings.Builder // demangled string being built
+       skip          bool            // don't print, just skip
+       lifetimes     int64           // number of bound lifetimes
+       last          byte            // last byte written to buffer
+       noGenericArgs bool            // don't demangle generic arguments
+       max           int             // maximum output length
 }
 
 // fail panics with demangleErr, to be caught in rustToString.
@@ -104,6 +119,10 @@ func (rst *rustState) writeByte(c byte) {
        if rst.skip {
                return
        }
+       if rst.max > 0 && rst.buf.Len() > rst.max {
+               rst.skip = true
+               return
+       }
        rst.last = c
        rst.buf.WriteByte(c)
 }
@@ -113,6 +132,10 @@ func (rst *rustState) writeString(s string) {
        if rst.skip {
                return
        }
+       if rst.max > 0 && rst.buf.Len() > rst.max {
+               rst.skip = true
+               return
+       }
        if len(s) > 0 {
                rst.last = s[len(s)-1]
                rst.buf.WriteString(s)
@@ -232,15 +255,7 @@ func (rst *rustState) path(needsSeparator bool) {
                        rst.writeString("::")
                }
                rst.writeByte('<')
-               first := true
-               for len(rst.str) > 0 && rst.str[0] != 'E' {
-                       if first {
-                               first = false
-                       } else {
-                               rst.writeString(", ")
-                       }
-                       rst.genericArg()
-               }
+               rst.genericArgs()
                rst.writeByte('>')
                rst.checkChar('E')
        case 'B':
@@ -436,6 +451,27 @@ func (rst *rustState) expandPunycode(s string) string {
        return string(output)
 }
 
+// genericArgs prints a list of generic arguments, without angle brackets.
+func (rst *rustState) genericArgs() {
+       if rst.noGenericArgs {
+               hold := rst.skip
+               rst.skip = true
+               defer func() {
+                       rst.skip = hold
+               }()
+       }
+
+       first := true
+       for len(rst.str) > 0 && rst.str[0] != 'E' {
+               if first {
+                       first = false
+               } else {
+                       rst.writeString(", ")
+               }
+               rst.genericArg()
+       }
+}
+
 // genericArg parses:
 //
 //     <generic-arg> = <lifetime>
@@ -724,15 +760,7 @@ func (rst *rustState) pathStartGenerics() bool {
                rst.advance(1)
                rst.path(false)
                rst.writeByte('<')
-               first := true
-               for len(rst.str) > 0 && rst.str[0] != 'E' {
-                       if first {
-                               first = false
-                       } else {
-                               rst.writeString(", ")
-                       }
-                       rst.genericArg()
-               }
+               rst.genericArgs()
                rst.checkChar('E')
                return true
        case 'B':
@@ -944,6 +972,9 @@ func (rst *rustState) backref(demangle func()) {
        if rst.skip {
                return
        }
+       if rst.max > 0 && rst.buf.Len() > rst.max {
+               return
+       }
 
        idx := int(idx64)
        if int64(idx) != idx64 {
@@ -986,6 +1017,13 @@ func (rst *rustState) decimalNumber() int {
 // oldRustToString demangles a Rust symbol using the old demangling.
 // The second result reports whether this is a valid Rust mangled name.
 func oldRustToString(name string, options []Option) (string, bool) {
+       max := 0
+       for _, o := range options {
+               if isMaxLength(o) {
+                       max = maxLength(o)
+               }
+       }
+
        // We know that the string starts with _ZN.
        name = name[3:]
 
@@ -1019,6 +1057,10 @@ func oldRustToString(name string, options []Option) (string, bool) {
        // The name is a sequence of length-preceded identifiers.
        var sb strings.Builder
        for len(name) > 0 {
+               if max > 0 && sb.Len() > max {
+                       break
+               }
+
                if !isDigit(name[0]) {
                        return "", false
                }
@@ -1115,5 +1157,9 @@ func oldRustToString(name string, options []Option) (string, bool) {
                }
        }
 
-       return sb.String(), true
+       s := sb.String()
+       if max > 0 && len(s) > max {
+               s = s[:max]
+       }
+       return s, true
 }
index b740954219001152eaf40a124c7c4e6c89f232ad..155d95b335a1971d36099b175b9d64ddcf51bb48 100644 (file)
@@ -1,5 +1,5 @@
-# github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26
-## explicit; go 1.18
+# github.com/google/pprof v0.0.0-20230811205829-9131a7e9cc17
+## explicit; go 1.19
 github.com/google/pprof/driver
 github.com/google/pprof/internal/binutils
 github.com/google/pprof/internal/driver
@@ -14,8 +14,8 @@ github.com/google/pprof/internal/transport
 github.com/google/pprof/profile
 github.com/google/pprof/third_party/d3flamegraph
 github.com/google/pprof/third_party/svgpan
-# github.com/ianlancetaylor/demangle v0.0.0-20220319035150-800ac71e25c2
-## explicit; go 1.12
+# github.com/ianlancetaylor/demangle v0.0.0-20230524184225-eabc099b10ab
+## explicit; go 1.13
 github.com/ianlancetaylor/demangle
 # golang.org/x/arch v0.4.0
 ## explicit; go 1.17