Brings in the latest github.com/google/pprof.
Fixes #67626
Change-Id: Id8faef20f0a9bf81dd117110bf540aca852db6be
Cq-Include-Trybots: luci.golang.try:gotip-linux-amd64-longtest
Reviewed-on: https://go-review.googlesource.com/c/go/+/588655
Reviewed-by: Carlos Amedee <carlos@golang.org>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Auto-Submit: Dmitri Shuralyov <dmitshur@golang.org>
Reviewed-by: Dmitri Shuralyov <dmitshur@google.com>
Reviewed-by: Dmitri Shuralyov <dmitshur@golang.org>
go 1.23
require (
- github.com/google/pprof v0.0.0-20240207164012-fb44976bdcd5
+ github.com/google/pprof v0.0.0-20240528025155-186aa0362fba
golang.org/x/arch v0.7.0
golang.org/x/build v0.0.0-20240222153247-cf4ed81bb19f
golang.org/x/mod v0.17.1-0.20240514174713-c0bdc7bd01c9
)
require (
- github.com/ianlancetaylor/demangle v0.0.0-20230524184225-eabc099b10ab // indirect
+ github.com/ianlancetaylor/demangle v0.0.0-20240312041847-bd984b5ce465 // indirect
golang.org/x/text v0.14.0 // indirect
rsc.io/markdown v0.0.0-20240117044121-669d2fdf1650 // indirect
)
-github.com/chromedp/cdproto v0.0.0-20230802225258-3cf4e6d46a89 h1:aPflPkRFkVwbW6dmcVqfgwp1i+UWGFH6VgR1Jim5Ygc=
-github.com/chromedp/cdproto v0.0.0-20230802225258-3cf4e6d46a89/go.mod h1:GKljq0VrfU4D5yc+2qA6OVr8pmO/MBbPEWqWQ/oqGEs=
-github.com/chromedp/chromedp v0.9.2 h1:dKtNz4kApb06KuSXoTQIyUC2TrA0fhGDwNZf3bcgfKw=
-github.com/chromedp/chromedp v0.9.2/go.mod h1:LkSXJKONWTCHAfQasKFUZI+mxqS4tZqhmtGzzhLsnLs=
-github.com/chromedp/sysutil v1.0.0 h1:+ZxhTpfpZlmchB58ih/LBHX52ky7w2VhQVKQMucy3Ic=
-github.com/chromedp/sysutil v1.0.0/go.mod h1:kgWmDdq8fTzXYcKIBqIYvRRTnYb9aNS9moAV0xufSww=
-github.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU=
-github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM=
-github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og=
-github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
-github.com/gobwas/ws v1.2.1 h1:F2aeBZrm2NDsc7vbovKrWSogd4wvfAxg0FQ89/iqOTk=
-github.com/gobwas/ws v1.2.1/go.mod h1:hRKAFb8wOxFROYNsT1bqfWnhX+b5MFeJM9r2ZSwg/KY=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
-github.com/google/pprof v0.0.0-20240207164012-fb44976bdcd5 h1:E/LAvt58di64hlYjx7AsNS6C/ysHWYo+2qPCZKTQhRo=
-github.com/google/pprof v0.0.0-20240207164012-fb44976bdcd5/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik=
-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=
-github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
-github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
-github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
-github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
+github.com/google/pprof v0.0.0-20240528025155-186aa0362fba h1:ql1qNgCyOB7iAEk8JTNM+zJrgIbnyCKX/wdlyPufP5g=
+github.com/google/pprof v0.0.0-20240528025155-186aa0362fba/go.mod h1:K1liHPHnj73Fdn/EKuT8nrFqBihUSKXoLYU0BuatOYo=
+github.com/ianlancetaylor/demangle v0.0.0-20240312041847-bd984b5ce465 h1:KwWnWVWCNtNq/ewIX7HIKnELmEx2nDP42yskD/pi7QE=
+github.com/ianlancetaylor/demangle v0.0.0-20240312041847-bd984b5ce465/go.mod h1:gx7rwoVhcfuVKG5uya9Hs3Sxj7EIvldVofAWIUtGouw=
github.com/yuin/goldmark v1.6.0 h1:boZcn2GTjpsynOsC0iJHnBWa4Bi0qzfJjthwauItG68=
github.com/yuin/goldmark v1.6.0/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
golang.org/x/arch v0.7.0 h1:pskyeJh/3AmoQ8CPE95vxHLqp1G1GfGNXTmcl9NEKTc=
// A UI manages user interactions.
type UI interface {
- // Read returns a line of text (a command) read from the user.
+ // ReadLine returns a line of text (a command) read from the user.
// prompt is printed before reading the command.
ReadLine(prompt string) (string, error)
defer ef.Close()
buildID := ""
- if f, err := os.Open(name); err == nil {
- if id, err := elfexec.GetBuildID(f); err == nil {
- buildID = fmt.Sprintf("%x", id)
- }
+ if id, err := elfexec.GetBuildID(ef); err == nil {
+ buildID = fmt.Sprintf("%x", id)
}
var (
import (
"bytes"
"fmt"
+ "io"
"os"
"path/filepath"
"regexp"
// Generate the report.
dst := new(bytes.Buffer)
- if err := report.Generate(dst, rpt, o.Obj); err != nil {
+ switch rpt.OutputFormat() {
+ case report.WebList:
+ // We need template expansion, so generate here instead of in report.
+ err = printWebList(dst, rpt, o.Obj)
+ default:
+ err = report.Generate(dst, rpt, o.Obj)
+ }
+ if err != nil {
return err
}
src := dst
return out.Close()
}
+func printWebList(dst io.Writer, rpt *report.Report, obj plugin.ObjTool) error {
+ listing, err := report.MakeWebList(rpt, obj, -1)
+ if err != nil {
+ return err
+ }
+ legend := report.ProfileLabels(rpt)
+ return renderHTML(dst, "sourcelisting", rpt, nil, legend, webArgs{
+ Standalone: true,
+ Listing: listing,
+ })
+}
+
func applyCommandOverrides(cmd string, outputFormat int, cfg config) config {
// Some report types override the trim flag to false below. This is to make
// sure the default heuristics of excluding insignificant nodes and edges
var f io.ReadCloser
// First determine whether the source is a file, if not, it will be treated as a URL.
- if _, openErr := os.Stat(source); openErr == nil {
+ if _, err = os.Stat(source); err == nil {
if isPerfFile(source) {
f, err = convertPerfData(source, ui)
} else {
--- /dev/null
+#graph {
+ cursor: grab;
+}
+
+#graph:active {
+ cursor: grabbing;
+}
<meta charset="utf-8">
<title>{{.Title}}</title>
{{template "css" .}}
+ {{template "graph_css" .}}
</head>
<body>
{{template "header" .}}
<head>
<meta charset="utf-8">
<title>{{.Title}}</title>
- {{template "css" .}}
+ {{if not .Standalone}}{{template "css" .}}{{end}}
{{template "weblistcss" .}}
{{template "weblistjs" .}}
</head>
-<body>
- {{template "header" .}}
- <div id="content" class="source">
- {{.HTMLBody}}
- </div>
- {{template "script" .}}
- <script>viewer(new URL(window.location.href), null);</script>
+<body>{{"\n" -}}
+ {{/* emit different header in standalone mode */ -}}
+ {{if .Standalone}}{{"\n" -}}
+ <div class="legend">{{"" -}}
+ {{range $i, $e := .Legend -}}
+ {{if $i}}<br>{{"\n"}}{{end}}{{. -}}
+ {{end}}<br>Total: {{.Listing.Total -}}
+ </div>{{"" -}}
+ {{else -}}
+ {{template "header" .}}
+ <div id="content" class="source">{{"" -}}
+ {{end -}}
+
+ {{range .Listing.Files -}}
+ {{range .Funcs -}}
+ <h2>{{.Name}}</h2>{{"" -}}
+ <p class="filename">{{.File}}</p>{{"\n" -}}
+ <pre onClick="pprof_toggle_asm(event)">{{"\n" -}}
+ {{printf " Total: %10s %10s (flat, cum) %s" .Flat .Cumulative .Percent -}}
+ {{range .Lines -}}{{"\n" -}}
+ {{/* source line */ -}}
+ <span class=line>{{printf " %6d" .Line}}</span>{{" " -}}
+ <span class={{.HTMLClass}}>
+ {{- printf " %10s %10s %8s %s " .Flat .Cumulative "" .SrcLine -}}
+ </span>{{"" -}}
+
+ {{if .Instructions -}}
+ {{/* instructions for this source line */ -}}
+ <span class=asm>{{"" -}}
+ {{range .Instructions -}}
+ {{/* separate when we hit a new basic block */ -}}
+ {{if .NewBlock -}}{{printf " %8s %28s\n" "" "⋮"}}{{end -}}
+
+ {{/* inlined calls leading to this instruction */ -}}
+ {{range .InlinedCalls -}}
+ {{printf " %8s %10s %10s %8s " "" "" "" "" -}}
+ <span class=inlinesrc>{{.SrcLine}}</span>{{" " -}}
+ <span class=unimportant>{{.FileBase}}:{{.Line}}</span>{{"\n" -}}
+ {{end -}}
+
+ {{if not .Synthetic -}}
+ {{/* disassembled instruction */ -}}
+ {{printf " %8s %10s %10s %8x: %s " "" .Flat .Cumulative .Address .Disasm -}}
+ <span class=unimportant>{{.FileLine}}</span>{{"\n" -}}
+ {{end -}}
+ {{end -}}
+ </span>{{"" -}}
+ {{end -}}
+ {{/* end of line */ -}}
+ {{end}}{{"\n" -}}
+ </pre>{{"\n" -}}
+ {{/* end of function */ -}}
+ {{end -}}
+ {{/* end of file */ -}}
+ {{end -}}
+
+ {{if not .Standalone}}{{"\n " -}}
+ </div>{{"\n" -}}
+ {{template "script" .}}{{"\n" -}}
+ <script>viewer(new URL(window.location.href), null);</script>{{"" -}}
+ {{end}}
</body>
</html>
{{template "script" .}}
{{template "stacks_js"}}
<script>
+ pprofUnitDefs = {{.UnitDefs}};
stackViewer({{.Stacks}}, {{.Nodes}});
</script>
</body>
const FONT_SIZE = 12;
const MIN_FONT_SIZE = 8;
- // Mapping from unit to a list of display scales/labels.
- // List should be ordered by increasing unit size.
- const UNITS = new Map([
- ['B', [
- ['B', 1],
- ['kB', Math.pow(2, 10)],
- ['MB', Math.pow(2, 20)],
- ['GB', Math.pow(2, 30)],
- ['TB', Math.pow(2, 40)],
- ['PB', Math.pow(2, 50)]]],
- ['s', [
- ['ns', 1e-9],
- ['µs', 1e-6],
- ['ms', 1e-3],
- ['s', 1],
- ['hrs', 60*60]]]]);
-
// Fields
let pivots = []; // Indices of currently selected data.Sources entries.
let matches = new Set(); // Indices of sources that match search
// 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);
- if (list) {
- // Find first entry in list that is not too small.
- for (const [name, scale] of list) {
- if (v <= 100*scale) {
- v /= scale;
- unit = name;
- break;
- }
- }
- }
- return sign + Number(v.toFixed(2)) + unit;
+ return pprofUnitText(value*stacks.Scale, stacks.Unit);
}
function find(name) {
return hsl;
}
}
+
+// pprofUnitText returns a formatted string to display for value in the specified unit.
+function pprofUnitText(value, unit) {
+ const sign = (value < 0) ? "-" : "";
+ let v = Math.abs(value);
+ // Rescale to appropriate display unit.
+ let list = null;
+ for (const def of pprofUnitDefs) {
+ if (def.DefaultUnit.CanonicalName == unit) {
+ list = def.Units;
+ v *= def.DefaultUnit.Factor;
+ break;
+ }
+ }
+ if (list) {
+ // Stop just before entry that is too large.
+ for (let i = 0; i < list.length; i++) {
+ if (i == list.length-1 || list[i+1].Factor > v) {
+ v /= list[i].Factor;
+ unit = list[i].CanonicalName;
+ break;
+ }
+ }
+ }
+ return sign + Number(v.toFixed(2)) + unit;
+}
UserConfig: (i != 0),
}
}
- // Mark the last matching config as currennt
+ // Mark the last matching config as current
if lastMatch >= 0 {
result[lastMatch].Current = true
}
"html/template"
"net/http"
+ "github.com/google/pprof/internal/measurement"
"github.com/google/pprof/internal/report"
)
_, legend := report.TextItems(rpt)
ui.render(w, req, "stacks", rpt, errList, legend, webArgs{
- Stacks: template.JS(b),
- Nodes: nodes,
+ Stacks: template.JS(b),
+ Nodes: nodes,
+ UnitDefs: measurement.UnitTypes,
})
}
"fmt"
"html/template"
"os"
+ "sync"
+
+ "github.com/google/pprof/internal/report"
+)
+
+var (
+ htmlTemplates *template.Template // Lazily loaded templates
+ htmlTemplateInit sync.Once
)
+// getHTMLTemplates returns the set of HTML templates used by pprof,
+// initializing them if necessary.
+func getHTMLTemplates() *template.Template {
+ htmlTemplateInit.Do(func() {
+ htmlTemplates = template.New("templategroup")
+ addTemplates(htmlTemplates)
+ report.AddSourceTemplates(htmlTemplates)
+ })
+ return htmlTemplates
+}
+
//go:embed html
var embeddedFiles embed.FS
def("css", loadCSS("html/common.css"))
def("header", loadFile("html/header.html"))
def("graph", loadFile("html/graph.html"))
+ def("graph_css", loadCSS("html/graph.css"))
def("script", loadJS("html/common.js"))
def("top", loadFile("html/top.html"))
def("sourcelisting", loadFile("html/source.html"))
"bytes"
"fmt"
"html/template"
+ "io"
"net"
"net/http"
gourl "net/url"
"time"
"github.com/google/pprof/internal/graph"
+ "github.com/google/pprof/internal/measurement"
"github.com/google/pprof/internal/plugin"
"github.com/google/pprof/internal/report"
"github.com/google/pprof/profile"
copier profileCopier
options *plugin.Options
help map[string]string
- templates *template.Template
settingsFile string
}
if err != nil {
return nil, err
}
- templates := template.New("templategroup")
- addTemplates(templates)
- report.AddSourceTemplates(templates)
return &webInterface{
prof: p,
copier: copier,
options: opt,
help: make(map[string]string),
- templates: templates,
settingsFile: settingsFile,
}, nil
}
Total int64
SampleTypes []string
Legend []string
+ Standalone bool // True for command-line generation of HTML
Help map[string]string
Nodes []string
HTMLBody template.HTML
TextBody string
Top []report.TextItem
+ Listing report.WebListData
FlameGraph template.JS
Stacks template.JS
Configs []configMenuEntry
+ UnitDefs []measurement.UnitType
}
func serveWebInterface(hostport string, p *profile.Profile, o *plugin.Options, disableBrowser bool) error {
return rpt, catcher.errors
}
-// render generates html using the named template based on the contents of data.
-func (ui *webInterface) render(w http.ResponseWriter, req *http.Request, tmpl string,
- rpt *report.Report, errList, legend []string, data webArgs) {
+// renderHTML generates html using the named template based on the contents of data.
+func renderHTML(dst io.Writer, tmpl string, rpt *report.Report, errList, legend []string, data webArgs) error {
file := getFromLegend(legend, "File: ", "unknown")
profile := getFromLegend(legend, "Type: ", "unknown")
data.Title = file + " " + profile
data.Errors = errList
data.Total = rpt.Total()
- data.SampleTypes = sampleTypes(ui.prof)
data.Legend = legend
+ return getHTMLTemplates().ExecuteTemplate(dst, tmpl, data)
+}
+
+// render responds with html generated by passing data to the named template.
+func (ui *webInterface) render(w http.ResponseWriter, req *http.Request, tmpl string,
+ rpt *report.Report, errList, legend []string, data webArgs) {
+ data.SampleTypes = sampleTypes(ui.prof)
data.Help = ui.help
data.Configs = configMenu(ui.settingsFile, *req.URL)
-
html := &bytes.Buffer{}
- if err := ui.templates.ExecuteTemplate(html, tmpl, data); err != nil {
+ if err := renderHTML(html, tmpl, rpt, errList, legend, data); err != nil {
http.Error(w, "internal template error", http.StatusInternalServerError)
ui.options.UI.PrintErr(err)
return
}
// Generate source listing.
- var body bytes.Buffer
- if err := report.PrintWebList(&body, rpt, ui.options.Obj, maxEntries); err != nil {
+ listing, err := report.MakeWebList(rpt, ui.options.Obj, maxEntries)
+ if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
ui.options.UI.PrintErr(err)
return
legend := report.ProfileLabels(rpt)
ui.render(w, req, "sourcelisting", rpt, errList, legend, webArgs{
- HTMLBody: template.HTML(body.String()),
+ Listing: listing,
})
}
//
// If no build-ID was found but the binary was read without error, it returns
// (nil, nil).
-func GetBuildID(binary io.ReaderAt) ([]byte, error) {
- f, err := elf.NewFile(binary)
- if err != nil {
- return nil, err
- }
-
+func GetBuildID(f *elf.File) ([]byte, error) {
findBuildID := func(notes []elfNote) ([]byte, error) {
var buildID []byte
for _, note := range notes {
}
}
- nodes := make(Nodes, len(prof.Location))
+ nodes := make(Nodes, 0, len(prof.Location))
for _, nm := range parentNodeMap {
nodes = append(nodes, nm.nodes()...)
}
if v1.Unit == v2.Unit {
return true
}
- for _, ut := range unitTypes {
+ for _, ut := range UnitTypes {
if ut.sniffUnit(v1.Unit) != nil && ut.sniffUnit(v2.Unit) != nil {
return true
}
v, u := Scale(-value, fromUnit, toUnit)
return -v, u
}
- for _, ut := range unitTypes {
+ for _, ut := range UnitTypes {
if v, u, ok := ut.convertUnit(value, fromUnit, toUnit); ok {
return v, u
}
}
}
-// unit includes a list of aliases representing a specific unit and a factor
+// Unit includes a list of aliases representing a specific unit and a factor
// which one can multiple a value in the specified unit by to get the value
// in terms of the base unit.
-type unit struct {
- canonicalName string
+type Unit struct {
+ CanonicalName string
aliases []string
- factor float64
+ Factor float64
}
-// unitType includes a list of units that are within the same category (i.e.
+// UnitType includes a list of units that are within the same category (i.e.
// memory or time units) and a default unit to use for this type of unit.
-type unitType struct {
- defaultUnit unit
- units []unit
+type UnitType struct {
+ DefaultUnit Unit
+ Units []Unit
}
// findByAlias returns the unit associated with the specified alias. It returns
// nil if the unit with such alias is not found.
-func (ut unitType) findByAlias(alias string) *unit {
- for _, u := range ut.units {
+func (ut UnitType) findByAlias(alias string) *Unit {
+ for _, u := range ut.Units {
for _, a := range u.aliases {
if alias == a {
return &u
// sniffUnit simpifies the input alias and returns the unit associated with the
// specified alias. It returns nil if the unit with such alias is not found.
-func (ut unitType) sniffUnit(unit string) *unit {
+func (ut UnitType) sniffUnit(unit string) *Unit {
unit = strings.ToLower(unit)
if len(unit) > 2 {
unit = strings.TrimSuffix(unit, "s")
// autoScale takes in the value with units of the base unit and returns
// that value scaled to a reasonable unit if a reasonable unit is
// found.
-func (ut unitType) autoScale(value float64) (float64, string, bool) {
+func (ut UnitType) autoScale(value float64) (float64, string, bool) {
var f float64
var unit string
- for _, u := range ut.units {
- if u.factor >= f && (value/u.factor) >= 1.0 {
- f = u.factor
- unit = u.canonicalName
+ for _, u := range ut.Units {
+ if u.Factor >= f && (value/u.Factor) >= 1.0 {
+ f = u.Factor
+ unit = u.CanonicalName
}
}
if f == 0 {
// included in the unitType, then a false boolean will be returned. If the
// toUnit is not in the unitType, the value will be returned in terms of the
// default unitType.
-func (ut unitType) convertUnit(value int64, fromUnitStr, toUnitStr string) (float64, string, bool) {
+func (ut UnitType) convertUnit(value int64, fromUnitStr, toUnitStr string) (float64, string, bool) {
fromUnit := ut.sniffUnit(fromUnitStr)
if fromUnit == nil {
return 0, "", false
}
- v := float64(value) * fromUnit.factor
+ v := float64(value) * fromUnit.Factor
if toUnitStr == "minimum" || toUnitStr == "auto" {
if v, u, ok := ut.autoScale(v); ok {
return v, u, true
}
- return v / ut.defaultUnit.factor, ut.defaultUnit.canonicalName, true
+ return v / ut.DefaultUnit.Factor, ut.DefaultUnit.CanonicalName, true
}
toUnit := ut.sniffUnit(toUnitStr)
if toUnit == nil {
- return v / ut.defaultUnit.factor, ut.defaultUnit.canonicalName, true
+ return v / ut.DefaultUnit.Factor, ut.DefaultUnit.CanonicalName, true
}
- return v / toUnit.factor, toUnit.canonicalName, true
+ return v / toUnit.Factor, toUnit.CanonicalName, true
}
-var unitTypes = []unitType{{
- units: []unit{
+// UnitTypes holds the definition of units known to pprof.
+var UnitTypes = []UnitType{{
+ Units: []Unit{
{"B", []string{"b", "byte"}, 1},
{"kB", []string{"kb", "kbyte", "kilobyte"}, float64(1 << 10)},
{"MB", []string{"mb", "mbyte", "megabyte"}, float64(1 << 20)},
{"TB", []string{"tb", "tbyte", "terabyte"}, float64(1 << 40)},
{"PB", []string{"pb", "pbyte", "petabyte"}, float64(1 << 50)},
},
- defaultUnit: unit{"B", []string{"b", "byte"}, 1},
+ DefaultUnit: Unit{"B", []string{"b", "byte"}, 1},
}, {
- units: []unit{
+ Units: []Unit{
{"ns", []string{"ns", "nanosecond"}, float64(time.Nanosecond)},
{"us", []string{"μs", "us", "microsecond"}, float64(time.Microsecond)},
{"ms", []string{"ms", "millisecond"}, float64(time.Millisecond)},
{"s", []string{"s", "sec", "second"}, float64(time.Second)},
{"hrs", []string{"hour", "hr"}, float64(time.Hour)},
},
- defaultUnit: unit{"s", []string{}, float64(time.Second)},
+ DefaultUnit: Unit{"s", []string{}, float64(time.Second)},
}, {
- units: []unit{
+ Units: []Unit{
{"n*GCU", []string{"nanogcu"}, 1e-9},
{"u*GCU", []string{"microgcu"}, 1e-6},
{"m*GCU", []string{"milligcu"}, 1e-3},
{"T*GCU", []string{"teragcu"}, 1e12},
{"P*GCU", []string{"petagcu"}, 1e15},
},
- defaultUnit: unit{"GCU", []string{}, 1.0},
+ DefaultUnit: Unit{"GCU", []string{}, 1.0},
}}
// A UI manages user interactions.
type UI interface {
- // Read returns a line of text (a command) read from the user.
+ // ReadLine returns a line of text (a command) read from the user.
// prompt is printed before reading the command.
ReadLine(prompt string) (string, error)
return printAssembly(w, rpt, obj)
case List:
return printSource(w, rpt)
- case WebList:
- return printWebSource(w, rpt, obj)
case Callgrind:
return printCallgrind(w, rpt)
}
- return fmt.Errorf("unexpected output format")
+ // Note: WebList handling is in driver package.
+ return fmt.Errorf("unexpected output format %v", o.OutputFormat)
}
// newTrimmedGraph creates a graph for this report, trimmed according
// Total returns the total number of samples in a report.
func (rpt *Report) Total() int64 { return rpt.total }
+// OutputFormat returns the output format for the report.
+func (rpt *Report) OutputFormat() int { return rpt.options.OutputFormat }
+
func abs64(i int64) int64 {
if i < 0 {
return -i
return nil
}
-// printWebSource prints an annotated source listing, include all
-// functions with samples that match the regexp rpt.options.symbol.
-func printWebSource(w io.Writer, rpt *Report, obj plugin.ObjTool) error {
- printHeader(w, rpt)
- if err := PrintWebList(w, rpt, obj, -1); err != nil {
- return err
- }
- printPageClosing(w)
- return nil
-}
-
// sourcePrinter holds state needed for generating source+asm HTML listing.
type sourcePrinter struct {
reader *sourceReader
score int64 // Used to order ranges for processing
}
-// PrintWebList prints annotated source listing of rpt to w.
+// WebListData holds the data needed to generate HTML source code listing.
+type WebListData struct {
+ Total string
+ Files []WebListFile
+}
+
+// WebListFile holds the per-file information for HTML source code listing.
+type WebListFile struct {
+ Funcs []WebListFunc
+}
+
+// WebListFunc holds the per-function information for HTML source code listing.
+type WebListFunc struct {
+ Name string
+ File string
+ Flat string
+ Cumulative string
+ Percent string
+ Lines []WebListLine
+}
+
+// WebListLine holds the per-source-line information for HTML source code listing.
+type WebListLine struct {
+ SrcLine string
+ HTMLClass string
+ Line int
+ Flat string
+ Cumulative string
+ Instructions []WebListInstruction
+}
+
+// WebListInstruction holds the per-instruction information for HTML source code listing.
+type WebListInstruction struct {
+ NewBlock bool // Insert marker that indicates separation from previous block
+ Flat string
+ Cumulative string
+ Synthetic bool
+ Address uint64
+ Disasm string
+ FileLine string
+ InlinedCalls []WebListCall
+}
+
+// WebListCall holds the per-inlined-call information for HTML source code listing.
+type WebListCall struct {
+ SrcLine string
+ FileBase string
+ Line int
+}
+
+// MakeWebList returns an annotated source listing of rpt.
// rpt.prof should contain inlined call info.
-func PrintWebList(w io.Writer, rpt *Report, obj plugin.ObjTool, maxFiles int) error {
+func MakeWebList(rpt *Report, obj plugin.ObjTool, maxFiles int) (WebListData, error) {
sourcePath := rpt.options.SourcePath
if sourcePath == "" {
wd, err := os.Getwd()
if err != nil {
- return fmt.Errorf("could not stat current dir: %v", err)
+ return WebListData{}, fmt.Errorf("could not stat current dir: %v", err)
}
sourcePath = wd
}
sp := newSourcePrinter(rpt, obj, sourcePath)
if len(sp.interest) == 0 {
- return fmt.Errorf("no matches found for regexp: %s", rpt.options.Symbol)
+ return WebListData{}, fmt.Errorf("no matches found for regexp: %s", rpt.options.Symbol)
}
- sp.print(w, maxFiles, rpt)
- sp.close()
- return nil
+ defer sp.close()
+ return sp.generate(maxFiles, rpt), nil
}
func newSourcePrinter(rpt *Report, obj plugin.ObjTool, sourcePath string) *sourcePrinter {
}
}
-func (sp *sourcePrinter) print(w io.Writer, maxFiles int, rpt *Report) {
+func (sp *sourcePrinter) generate(maxFiles int, rpt *Report) WebListData {
// Finalize per-file counts.
for _, file := range sp.files {
seen := map[uint64]bool{}
maxFiles = len(files)
}
sort.Slice(files, order)
+ result := WebListData{
+ Total: rpt.formatValue(rpt.total),
+ }
for i, f := range files {
if i < maxFiles {
- sp.printFile(w, f, rpt)
+ result.Files = append(result.Files, sp.generateFile(f, rpt))
}
}
+ return result
}
-func (sp *sourcePrinter) printFile(w io.Writer, f *sourceFile, rpt *Report) {
+func (sp *sourcePrinter) generateFile(f *sourceFile, rpt *Report) WebListFile {
+ var result WebListFile
for _, fn := range sp.functions(f) {
if fn.cum == 0 {
continue
}
- printFunctionHeader(w, fn.name, f.fname, fn.flat, fn.cum, rpt)
+
+ listfn := WebListFunc{
+ Name: fn.name,
+ File: f.fname,
+ Flat: rpt.formatValue(fn.flat),
+ Cumulative: rpt.formatValue(fn.cum),
+ Percent: measurement.Percentage(fn.cum, rpt.total),
+ }
var asm []assemblyInstruction
for l := fn.begin; l < fn.end; l++ {
lineContents, ok := sp.reader.line(f.fname, l)
})
}
- printFunctionSourceLine(w, l, flatSum, cumSum, lineContents, asm, sp.reader, rpt)
+ listfn.Lines = append(listfn.Lines, makeWebListLine(l, flatSum, cumSum, lineContents, asm, sp.reader, rpt))
}
- printFunctionClosing(w)
+
+ result.Funcs = append(result.Funcs, listfn)
}
+ return result
}
// functions splits apart the lines to show in a file into a list of per-function ranges.
return object
}
-// printHeader prints the page header for a weblist report.
-func printHeader(w io.Writer, rpt *Report) {
- fmt.Fprintln(w, `
-<!DOCTYPE html>
-<html>
-<head>
-<meta charset="UTF-8">
-<title>Pprof listing</title>`)
- fmt.Fprintln(w, weblistPageCSS)
- fmt.Fprintln(w, weblistPageScript)
- fmt.Fprint(w, "</head>\n<body>\n\n")
-
- var labels []string
- for _, l := range ProfileLabels(rpt) {
- labels = append(labels, template.HTMLEscapeString(l))
+// makeWebListLine returns the contents of a single line in a web listing. This includes
+// the source line and the corresponding assembly.
+func makeWebListLine(lineNo int, flat, cum int64, lineContents string,
+ assembly []assemblyInstruction, reader *sourceReader, rpt *Report) WebListLine {
+ line := WebListLine{
+ SrcLine: lineContents,
+ Line: lineNo,
+ Flat: valueOrDot(flat, rpt),
+ Cumulative: valueOrDot(cum, rpt),
}
- fmt.Fprintf(w, `<div class="legend">%s<br>Total: %s</div>`,
- strings.Join(labels, "<br>\n"),
- rpt.formatValue(rpt.total),
- )
-}
-
-// printFunctionHeader prints a function header for a weblist report.
-func printFunctionHeader(w io.Writer, name, path string, flatSum, cumSum int64, rpt *Report) {
- fmt.Fprintf(w, `<h2>%s</h2><p class="filename">%s</p>
-<pre onClick="pprof_toggle_asm(event)">
- Total: %10s %10s (flat, cum) %s
-`,
- template.HTMLEscapeString(name), template.HTMLEscapeString(path),
- rpt.formatValue(flatSum), rpt.formatValue(cumSum),
- measurement.Percentage(cumSum, rpt.total))
-}
-
-// printFunctionSourceLine prints a source line and the corresponding assembly.
-func printFunctionSourceLine(w io.Writer, lineNo int, flat, cum int64, lineContents string,
- assembly []assemblyInstruction, reader *sourceReader, rpt *Report) {
if len(assembly) == 0 {
- fmt.Fprintf(w,
- "<span class=line> %6d</span> <span class=nop> %10s %10s %8s %s </span>\n",
- lineNo,
- valueOrDot(flat, rpt), valueOrDot(cum, rpt),
- "", template.HTMLEscapeString(lineContents))
- return
+ line.HTMLClass = "nop"
+ return line
}
nestedInfo := false
- cl := "deadsrc"
+ line.HTMLClass = "deadsrc"
for _, an := range assembly {
if len(an.inlineCalls) > 0 || an.instruction != synthAsm {
nestedInfo = true
- cl = "livesrc"
+ line.HTMLClass = "livesrc"
}
}
- fmt.Fprintf(w,
- "<span class=line> %6d</span> <span class=%s> %10s %10s %8s %s </span>",
- lineNo, cl,
- valueOrDot(flat, rpt), valueOrDot(cum, rpt),
- "", template.HTMLEscapeString(lineContents))
if nestedInfo {
srcIndent := indentation(lineContents)
- printNested(w, srcIndent, assembly, reader, rpt)
+ line.Instructions = makeWebListInstructions(srcIndent, assembly, reader, rpt)
}
- fmt.Fprintln(w)
+ return line
}
-func printNested(w io.Writer, srcIndent int, assembly []assemblyInstruction, reader *sourceReader, rpt *Report) {
- fmt.Fprint(w, "<span class=asm>")
+func makeWebListInstructions(srcIndent int, assembly []assemblyInstruction, reader *sourceReader, rpt *Report) []WebListInstruction {
+ var result []WebListInstruction
var curCalls []callID
for i, an := range assembly {
- if an.startsBlock && i != 0 {
- // Insert a separator between discontiguous blocks.
- fmt.Fprintf(w, " %8s %28s\n", "", "⋮")
- }
-
var fileline string
if an.file != "" {
fileline = fmt.Sprintf("%s:%d", template.HTMLEscapeString(filepath.Base(an.file)), an.line)
}
- flat, cum := an.flat, an.cum
+ text := strings.Repeat(" ", srcIndent+4+4*len(an.inlineCalls)) + an.instruction
+ inst := WebListInstruction{
+ NewBlock: (an.startsBlock && i != 0),
+ Flat: valueOrDot(an.flat, rpt),
+ Cumulative: valueOrDot(an.cum, rpt),
+ Synthetic: (an.instruction == synthAsm),
+ Address: an.address,
+ Disasm: rightPad(text, 80),
+ FileLine: fileline,
+ }
- // Print inlined call context.
+ // Add inlined call context.
for j, c := range an.inlineCalls {
if j < len(curCalls) && curCalls[j] == c {
// Skip if same as previous instruction.
if !ok {
fline = ""
}
- text := strings.Repeat(" ", srcIndent+4+4*j) + strings.TrimSpace(fline)
- fmt.Fprintf(w, " %8s %10s %10s %8s <span class=inlinesrc>%s</span> <span class=unimportant>%s:%d</span>\n",
- "", "", "", "",
- template.HTMLEscapeString(rightPad(text, 80)),
- template.HTMLEscapeString(filepath.Base(c.file)), c.line)
+ srcCode := strings.Repeat(" ", srcIndent+4+4*j) + strings.TrimSpace(fline)
+ inst.InlinedCalls = append(inst.InlinedCalls, WebListCall{
+ SrcLine: rightPad(srcCode, 80),
+ FileBase: filepath.Base(c.file),
+ Line: c.line,
+ })
}
curCalls = an.inlineCalls
- if an.instruction == synthAsm {
- continue
- }
- text := strings.Repeat(" ", srcIndent+4+4*len(curCalls)) + an.instruction
- fmt.Fprintf(w, " %8s %10s %10s %8x: %s <span class=unimportant>%s</span>\n",
- "", valueOrDot(flat, rpt), valueOrDot(cum, rpt), an.address,
- template.HTMLEscapeString(rightPad(text, 80)),
- // fileline should not be escaped since it was formed by appending
- // line number (just digits) to an escaped file name. Escaping here
- // would cause double-escaping of file name.
- fileline)
- }
- fmt.Fprint(w, "</span>")
-}
-
-// printFunctionClosing prints the end of a function in a weblist report.
-func printFunctionClosing(w io.Writer) {
- fmt.Fprintln(w, "</pre>")
-}
-// printPageClosing prints the end of the page in a weblist report.
-func printPageClosing(w io.Writer) {
- fmt.Fprintln(w, weblistPageClosing)
+ result = append(result, inst)
+ }
+ return result
}
// getSourceFromFile collects the sources of a function from a source
}
}
</script>`
-
-const weblistPageClosing = `
-</body>
-</html>`
}
}
- mt, err := newMapping(prof, obj, ui, force)
- if err != nil {
- return err
+ functions := map[profile.Function]*profile.Function{}
+ addFunction := func(f *profile.Function) *profile.Function {
+ if fp := functions[*f]; fp != nil {
+ return fp
+ }
+ functions[*f] = f
+ f.ID = uint64(len(prof.Function)) + 1
+ prof.Function = append(prof.Function, f)
+ return f
+ }
+
+ missingBinaries := false
+ mappingLocs := map[*profile.Mapping][]*profile.Location{}
+ for _, l := range prof.Location {
+ mappingLocs[l.Mapping] = append(mappingLocs[l.Mapping], l)
}
- defer mt.close()
+ for midx, m := range prof.Mapping {
+ locs := mappingLocs[m]
+ if len(locs) == 0 {
+ // The mapping is dangling and has no locations pointing to it.
+ continue
+ }
+ // Do not attempt to re-symbolize a mapping that has already been symbolized.
+ if !force && (m.HasFunctions || m.HasFilenames || m.HasLineNumbers) {
+ continue
+ }
+ if m.File == "" {
+ if midx == 0 {
+ ui.PrintErr("Main binary filename not available.")
+ continue
+ }
+ missingBinaries = true
+ continue
+ }
+ if m.Unsymbolizable() {
+ // Skip well-known system mappings
+ continue
+ }
+ if m.BuildID == "" {
+ if u, err := url.Parse(m.File); err == nil && u.IsAbs() && strings.Contains(strings.ToLower(u.Scheme), "http") {
+ // Skip mappings pointing to a source URL
+ continue
+ }
+ }
- functions := make(map[profile.Function]*profile.Function)
- for _, l := range mt.prof.Location {
- m := l.Mapping
- segment := mt.segments[m]
- if segment == nil {
- // Nothing to do.
+ name := filepath.Base(m.File)
+ if m.BuildID != "" {
+ name += fmt.Sprintf(" (build ID %s)", m.BuildID)
+ }
+ f, err := obj.Open(m.File, m.Start, m.Limit, m.Offset, m.KernelRelocationSymbol)
+ if err != nil {
+ ui.PrintErr("Local symbolization failed for ", name, ": ", err)
+ missingBinaries = true
continue
}
+ if fid := f.BuildID(); m.BuildID != "" && fid != "" && fid != m.BuildID {
+ ui.PrintErr("Local symbolization failed for ", name, ": build ID mismatch")
+ f.Close()
+ continue
+ }
+ symbolizeOneMapping(m, locs, f, addFunction)
+ f.Close()
+ }
- stack, err := segment.SourceLine(l.Address)
+ if missingBinaries {
+ ui.PrintErr("Some binary filenames not available. Symbolization may be incomplete.\n" +
+ "Try setting PPROF_BINARY_PATH to the search path for local binaries.")
+ }
+ return nil
+}
+
+func symbolizeOneMapping(m *profile.Mapping, locs []*profile.Location, obj plugin.ObjFile, addFunction func(*profile.Function) *profile.Function) {
+ for _, l := range locs {
+ stack, err := obj.SourceLine(l.Address)
if err != nil || len(stack) == 0 {
// No answers from addr2line.
continue
if frame.Line != 0 {
m.HasLineNumbers = true
}
- f := &profile.Function{
+ f := addFunction(&profile.Function{
Name: frame.Func,
SystemName: frame.Func,
Filename: frame.File,
- }
- if fp := functions[*f]; fp != nil {
- f = fp
- } else {
- functions[*f] = f
- f.ID = uint64(len(mt.prof.Function)) + 1
- mt.prof.Function = append(mt.prof.Function, f)
- }
+ })
l.Line[i] = profile.Line{
Function: f,
Line: int64(frame.Line),
m.HasInlineFrames = true
}
}
-
- return nil
}
// Demangle updates the function names in a profile with demangled C++
}
return name
}
-
-// newMapping creates a mappingTable for a profile.
-func newMapping(prof *profile.Profile, obj plugin.ObjTool, ui plugin.UI, force bool) (*mappingTable, error) {
- mt := &mappingTable{
- prof: prof,
- segments: make(map[*profile.Mapping]plugin.ObjFile),
- }
-
- // Identify used mappings
- mappings := make(map[*profile.Mapping]bool)
- for _, l := range prof.Location {
- mappings[l.Mapping] = true
- }
-
- missingBinaries := false
- for midx, m := range prof.Mapping {
- if !mappings[m] {
- continue
- }
-
- // Do not attempt to re-symbolize a mapping that has already been symbolized.
- if !force && (m.HasFunctions || m.HasFilenames || m.HasLineNumbers) {
- continue
- }
-
- if m.File == "" {
- if midx == 0 {
- ui.PrintErr("Main binary filename not available.")
- continue
- }
- missingBinaries = true
- continue
- }
-
- // Skip well-known system mappings
- if m.Unsymbolizable() {
- continue
- }
-
- // Skip mappings pointing to a source URL
- if m.BuildID == "" {
- if u, err := url.Parse(m.File); err == nil && u.IsAbs() && strings.Contains(strings.ToLower(u.Scheme), "http") {
- continue
- }
- }
-
- name := filepath.Base(m.File)
- if m.BuildID != "" {
- name += fmt.Sprintf(" (build ID %s)", m.BuildID)
- }
- f, err := obj.Open(m.File, m.Start, m.Limit, m.Offset, m.KernelRelocationSymbol)
- if err != nil {
- ui.PrintErr("Local symbolization failed for ", name, ": ", err)
- missingBinaries = true
- continue
- }
- if fid := f.BuildID(); m.BuildID != "" && fid != "" && fid != m.BuildID {
- ui.PrintErr("Local symbolization failed for ", name, ": build ID mismatch")
- f.Close()
- continue
- }
-
- mt.segments[m] = f
- }
- if missingBinaries {
- ui.PrintErr("Some binary filenames not available. Symbolization may be incomplete.\n" +
- "Try setting PPROF_BINARY_PATH to the search path for local binaries.")
- }
- return mt, nil
-}
-
-// mappingTable contains the mechanisms for symbolization of a
-// profile.
-type mappingTable struct {
- prof *profile.Profile
- segments map[*profile.Mapping]plugin.ObjFile
-}
-
-// close releases any external processes being used for the mapping.
-func (mt *mappingTable) close() {
- for _, segment := range mt.segments {
- segment.Close()
- }
-}
// "[vdso]", [vsyscall]" and some others, see the code.
func (m *Mapping) Unsymbolizable() bool {
name := filepath.Base(m.File)
- return strings.HasPrefix(name, "[") || strings.HasPrefix(name, "linux-vdso") || strings.HasPrefix(m.File, "/dev/dri/")
+ return strings.HasPrefix(name, "[") || strings.HasPrefix(name, "linux-vdso") || strings.HasPrefix(m.File, "/dev/dri/") || m.File == "//anon"
}
// Copy makes a fully independent copy of a profile.
enclosingParams: enclosingParams,
llvmStyle: llvmStyle,
max: max,
+ scopes: 1,
}
a.print(&ps)
s := ps.buf.String()
llvmStyle bool
max int // maximum output length
+ // The scopes field is used to avoid unnecessary parentheses
+ // around expressions that use > (or >>). It is incremented if
+ // we output a parenthesis or something else that means that >
+ // or >> won't be treated as ending a template. It starts out
+ // as 1, and is set to 0 when we start writing template
+ // arguments. We add parentheses around expressions using > if
+ // scopes is 0. The effect is that an expression with > gets
+ // parentheses if used as a template argument that is not
+ // inside some other set of parentheses.
+ scopes int
+
buf strings.Builder
last byte // Last byte written to buffer.
ps.printing = ps.printing[:len(ps.printing)-1]
}
+// printList prints a list of AST values separated by commas,
+// optionally skipping some.
+func (ps *printState) printList(args []AST, skip func(AST) bool) {
+ first := true
+ for _, a := range args {
+ if skip != nil && skip(a) {
+ continue
+ }
+ if !first {
+ ps.writeString(", ")
+ }
+
+ needsParen := false
+ if ps.llvmStyle {
+ if p, ok := a.(hasPrec); ok {
+ if p.prec() >= precComma {
+ needsParen = true
+ }
+ }
+ }
+ if needsParen {
+ ps.startScope('(')
+ }
+
+ ps.print(a)
+
+ if needsParen {
+ ps.endScope(')')
+ }
+
+ first = false
+ }
+}
+
+// startScope starts a scope. This is used to decide whether we need
+// to parenthesize an expression using > or >>.
+func (ps *printState) startScope(b byte) {
+ ps.scopes++
+ ps.writeByte(b)
+}
+
+// endScope closes a scope.
+func (ps *printState) endScope(b byte) {
+ ps.scopes--
+ ps.writeByte(b)
+}
+
+// precedence is used for operator precedence. This is used to avoid
+// unnecessary parentheses when printing expressions in the LLVM style.
+type precedence int
+
+// The precedence values, in order from high to low.
+const (
+ precPrimary precedence = iota
+ precPostfix
+ precUnary
+ precCast
+ precPtrMem
+ precMul
+ precAdd
+ precShift
+ precSpaceship
+ precRel
+ precEqual
+ precAnd
+ precXor
+ precOr
+ precLogicalAnd
+ precLogicalOr
+ precCond
+ precAssign
+ precComma
+ precDefault
+)
+
+// hasPrec matches the AST nodes that have a prec method that returns
+// the node's precedence.
+type hasPrec interface {
+ prec() precedence
+}
+
// Name is an unqualified name.
type Name struct {
Name string
return fmt.Sprintf("%*s%s%s", indent, "", field, n.Name)
}
+func (n *Name) prec() precedence {
+ return precPrimary
+}
+
// Typed is a typed name.
type Typed struct {
Name AST
q.Name.goString(indent+2, "Name: "))
}
+func (q *Qualified) prec() precedence {
+ return precPrimary
+}
+
// Template is a template with arguments.
type Template struct {
Name AST
ps.writeByte(' ')
}
+ scopes := ps.scopes
+ ps.scopes = 0
+
ps.writeByte('<')
- first := true
- for _, a := range t.Args {
- if ps.isEmpty(a) {
- continue
- }
- if !first {
- ps.writeString(", ")
- }
- ps.print(a)
- first = false
- }
- if ps.last == '>' {
+ ps.printList(t.Args, ps.isEmpty)
+ if ps.last == '>' && !ps.llvmStyle {
// Avoid syntactic ambiguity in old versions of C++.
ps.writeByte(' ')
}
ps.writeByte('>')
+
+ ps.scopes = scopes
}
func (t *Template) Traverse(fn func(AST) bool) {
return fmt.Sprintf("%*s%sLambdaAuto: Index %d", indent, "", field, la.Index)
}
+// TemplateParamQualifiedArg is used when the mangled name includes
+// both the template parameter declaration and the template argument.
+// See https://github.com/itanium-cxx-abi/cxx-abi/issues/47.
+type TemplateParamQualifiedArg struct {
+ Param AST
+ Arg AST
+}
+
+func (tpqa *TemplateParamQualifiedArg) print(ps *printState) {
+ // We only demangle the actual template argument.
+ // That is what the LLVM demangler does.
+ // The parameter disambiguates the argument,
+ // but is hopefully not required by a human reader.
+ ps.print(tpqa.Arg)
+}
+
+func (tpqa *TemplateParamQualifiedArg) Traverse(fn func(AST) bool) {
+ if fn(tpqa) {
+ tpqa.Param.Traverse(fn)
+ tpqa.Arg.Traverse(fn)
+ }
+}
+
+func (tpqa *TemplateParamQualifiedArg) Copy(fn func(AST) AST, skip func(AST) bool) AST {
+ if skip(tpqa) {
+ return nil
+ }
+ param := tpqa.Param.Copy(fn, skip)
+ arg := tpqa.Arg.Copy(fn, skip)
+ if param == nil && arg == nil {
+ return fn(tpqa)
+ }
+ if param == nil {
+ param = tpqa.Param
+ }
+ if arg == nil {
+ arg = tpqa.Arg
+ }
+ tpqa = &TemplateParamQualifiedArg{Param: param, Arg: arg}
+ if r := fn(tpqa); r != nil {
+ return r
+ }
+ return tpqa
+}
+
+func (tpqa *TemplateParamQualifiedArg) GoString() string {
+ return tpqa.goString(0, "")
+}
+
+func (tpqa *TemplateParamQualifiedArg) goString(indent int, field string) string {
+ return fmt.Sprintf("%*s%sTemplateParamQualifiedArg:\n%s\n%s", indent, "", field,
+ tpqa.Param.goString(indent+2, "Param: "),
+ tpqa.Arg.goString(indent+2, "Arg: "))
+}
+
// Qualifiers is an ordered list of type qualifiers.
type Qualifiers struct {
Qualifiers []AST
func (q *Qualifier) print(ps *printState) {
ps.writeString(q.Name)
if len(q.Exprs) > 0 {
- ps.writeByte('(')
+ ps.startScope('(')
first := true
for _, e := range q.Exprs {
if el, ok := e.(*ExprList); ok && len(el.Exprs) == 0 {
ps.print(e)
first = false
}
- ps.writeByte(')')
+ ps.endScope(')')
}
}
return fmt.Sprintf("%*s%sBuiltinType: %s", indent, "", field, bt.Name)
}
+func (bt *BuiltinType) prec() precedence {
+ return precPrimary
+}
+
// printBase is common print code for types that are printed with a
// simple suffix.
func printBase(ps *printState, qual, base AST) {
it.Base.goString(indent+2, ""))
}
+// SuffixType is an type with an arbitrary suffix.
+type SuffixType struct {
+ Base AST
+ Suffix string
+}
+
+func (st *SuffixType) print(ps *printState) {
+ printBase(ps, st, st.Base)
+}
+
+func (st *SuffixType) printInner(ps *printState) {
+ ps.writeByte(' ')
+ ps.writeString(st.Suffix)
+}
+
+func (st *SuffixType) Traverse(fn func(AST) bool) {
+ if fn(st) {
+ st.Base.Traverse(fn)
+ }
+}
+
+func (st *SuffixType) Copy(fn func(AST) AST, skip func(AST) bool) AST {
+ if skip(st) {
+ return nil
+ }
+ base := st.Base.Copy(fn, skip)
+ if base == nil {
+ return fn(st)
+ }
+ st = &SuffixType{Base: base, Suffix: st.Suffix}
+ if r := fn(st); r != nil {
+ return r
+ }
+ return st
+}
+
+func (st *SuffixType) GoString() string {
+ return st.goString(0, "")
+}
+
+func (st *SuffixType) goString(indent int, field string) string {
+ return fmt.Sprintf("%*s%sSuffixType: %s\n%s", indent, "", field,
+ st.Suffix, st.Base.goString(indent+2, "Base: "))
+}
+
+// TransformedType is a builtin type with a template argument.
+type TransformedType struct {
+ Name string
+ Base AST
+}
+
+func (tt *TransformedType) print(ps *printState) {
+ ps.writeString(tt.Name)
+ ps.startScope('(')
+ ps.print(tt.Base)
+ ps.endScope(')')
+}
+
+func (tt *TransformedType) Traverse(fn func(AST) bool) {
+ if fn(tt) {
+ tt.Base.Traverse(fn)
+ }
+}
+
+func (tt *TransformedType) Copy(fn func(AST) AST, skip func(AST) bool) AST {
+ if skip(tt) {
+ return nil
+ }
+ base := tt.Base.Copy(fn, skip)
+ if base == nil {
+ return fn(tt)
+ }
+ tt = &TransformedType{Name: tt.Name, Base: base}
+ if r := fn(tt); r != nil {
+ return r
+ }
+ return tt
+}
+
+func (tt *TransformedType) GoString() string {
+ return tt.goString(0, "")
+}
+
+func (tt *TransformedType) goString(indent int, field string) string {
+ return fmt.Sprintf("%*s%sTransformedType: %s\n%s", indent, "", field,
+ tt.Name, tt.Base.goString(indent+2, "Base: "))
+}
+
// VendorQualifier is a type qualified by a vendor-specific qualifier.
type VendorQualifier struct {
Qualifier AST
}
ps.printOneInner(nil)
} else {
- ps.writeString(" (")
+ ps.writeByte(' ')
+ ps.startScope('(')
ps.printInner(false)
- ps.writeByte(')')
+ ps.endScope(')')
}
}
ps.writeString(space)
if space && ps.last != ' ' {
ps.writeByte(' ')
}
- ps.writeByte('(')
+ ps.startScope('(')
}
save := ps.printInner(true)
if paren {
- ps.writeByte(')')
+ ps.endScope(')')
}
- ps.writeByte('(')
+ ps.startScope('(')
if !ft.ForLocalName || ps.enclosingParams {
first := true
for _, a := range ft.Args {
first = false
}
}
- ps.writeByte(')')
+ ps.endScope(')')
ps.inner = save
ps.printInner(false)
return fmt.Sprintf("%*s%sFunctionParam: %d", indent, "", field, fp.Index)
}
+func (fp *FunctionParam) prec() precedence {
+ return precPrimary
+}
+
// PtrMem is a pointer-to-member expression.
type PtrMem struct {
Class AST
return fmt.Sprintf("%*s%sBinaryFP: %d", indent, "", field, bfp.Bits)
}
+// BitIntType is the C++23 _BitInt(N) type.
+type BitIntType struct {
+ Size AST
+ Signed bool
+}
+
+func (bt *BitIntType) print(ps *printState) {
+ if !bt.Signed {
+ ps.writeString("unsigned ")
+ }
+ ps.writeString("_BitInt")
+ ps.startScope('(')
+ ps.print(bt.Size)
+ ps.endScope(')')
+}
+
+func (bt *BitIntType) Traverse(fn func(AST) bool) {
+ if fn(bt) {
+ bt.Size.Traverse(fn)
+ }
+}
+
+func (bt *BitIntType) Copy(fn func(AST) AST, skip func(AST) bool) AST {
+ if skip(bt) {
+ return nil
+ }
+ size := bt.Size.Copy(fn, skip)
+ if size == nil {
+ return fn(bt)
+ }
+ bt = &BitIntType{Size: size, Signed: bt.Signed}
+ if r := fn(bt); r != nil {
+ return r
+ }
+ return bt
+}
+
+func (bt *BitIntType) GoString() string {
+ return bt.goString(0, "")
+}
+
+func (bt *BitIntType) goString(indent int, field string) string {
+ return fmt.Sprintf("%*s%sBitIntType: Signed: %t\n%s", indent, "", field,
+ bt.Signed,
+ bt.Size.goString(indent+2, "Size: "))
+}
+
// VectorType is a vector type.
type VectorType struct {
Dimension AST
if !ps.llvmStyle {
ps.writeString(" ")
}
- ps.writeString("(")
+ ps.startScope('(')
ps.print(dt.Expr)
- ps.writeByte(')')
+ ps.endScope(')')
}
func (dt *Decltype) Traverse(fn func(AST) bool) {
// Operator is an operator.
type Operator struct {
- Name string
+ Name string
+ precedence precedence
}
func (op *Operator) print(ps *printState) {
return fmt.Sprintf("%*s%sOperator: %s", indent, "", field, op.Name)
}
+func (op *Operator) prec() precedence {
+ return op.precedence
+}
+
// Constructor is a constructor.
type Constructor struct {
Name AST
func (sp *SizeofPack) print(ps *printState) {
if ps.llvmStyle {
- ps.writeString("sizeof...(")
+ ps.writeString("sizeof...")
+ ps.startScope('(')
ps.print(sp.Pack)
- ps.writeByte(')')
+ ps.endScope(')')
} else {
ps.writeString(fmt.Sprintf("%d", len(sp.Pack.Args)))
}
// TemplateTemplateParam is a template template parameter that appears
// in a lambda with explicit template parameters.
type TemplateTemplateParam struct {
- Name AST
- Params []AST
+ Name AST
+ Params []AST
+ Constraint AST
}
func (ttp *TemplateTemplateParam) print(ps *printState) {
+ scopes := ps.scopes
+ ps.scopes = 0
+
ps.writeString("template<")
- for i, param := range ttp.Params {
- if i > 0 {
- ps.writeString(", ")
- }
- ps.print(param)
- }
+ ps.printList(ttp.Params, nil)
ps.writeString("> typename ")
+
+ ps.scopes = scopes
+
ps.print(ttp.Name)
+
+ if ttp.Constraint != nil {
+ ps.writeString(" requires ")
+ ps.print(ttp.Constraint)
+ }
}
func (ttp *TemplateTemplateParam) Traverse(fn func(AST) bool) {
for _, param := range ttp.Params {
param.Traverse(fn)
}
+ if ttp.Constraint != nil {
+ ttp.Constraint.Traverse(fn)
+ }
}
}
}
}
+ var constraint AST
+ if ttp.Constraint != nil {
+ constraint = ttp.Constraint.Copy(fn, skip)
+ if constraint == nil {
+ constraint = ttp.Constraint
+ } else {
+ changed = true
+ }
+ }
+
if !changed {
return fn(ttp)
}
ttp = &TemplateTemplateParam{
- Name: name,
- Params: params,
+ Name: name,
+ Params: params,
+ Constraint: constraint,
}
if r := fn(ttp); r != nil {
return r
params.WriteByte('\n')
params.WriteString(p.goString(indent+4, fmt.Sprintf("%d: ", i)))
}
- return fmt.Sprintf("%*s%sTemplateTemplateParam:\n%s\n%s", indent, "", field,
+ var constraint string
+ if ttp.Constraint == nil {
+ constraint = fmt.Sprintf("%*sConstraint: nil", indent+2, "")
+ } else {
+ constraint = ttp.Constraint.goString(indent+2, "Constraint: ")
+ }
+ return fmt.Sprintf("%*s%sTemplateTemplateParam:\n%s\n%s\n%s", indent, "", field,
ttp.Name.goString(indent+2, "Name: "),
- params.String())
+ params.String(),
+ constraint)
+}
+
+// ConstrainedTypeTemplateParam is a constrained template type
+// parameter declaration.
+type ConstrainedTypeTemplateParam struct {
+ Name AST
+ Constraint AST
+}
+
+func (cttp *ConstrainedTypeTemplateParam) print(ps *printState) {
+ ps.inner = append(ps.inner, cttp)
+ ps.print(cttp.Constraint)
+ if len(ps.inner) > 0 {
+ ps.writeByte(' ')
+ ps.print(cttp.Name)
+ ps.inner = ps.inner[:len(ps.inner)-1]
+ }
+}
+
+func (cttp *ConstrainedTypeTemplateParam) printInner(ps *printState) {
+ ps.print(cttp.Name)
+}
+
+func (cttp *ConstrainedTypeTemplateParam) Traverse(fn func(AST) bool) {
+ if fn(cttp) {
+ cttp.Name.Traverse(fn)
+ cttp.Constraint.Traverse(fn)
+ }
+}
+
+func (cttp *ConstrainedTypeTemplateParam) Copy(fn func(AST) AST, skip func(AST) bool) AST {
+ if skip(cttp) {
+ return nil
+ }
+ name := cttp.Name.Copy(fn, skip)
+ constraint := cttp.Constraint.Copy(fn, skip)
+ if name == nil && constraint == nil {
+ return fn(cttp)
+ }
+ if name == nil {
+ name = cttp.Name
+ }
+ if constraint == nil {
+ constraint = cttp.Constraint
+ }
+ cttp = &ConstrainedTypeTemplateParam{Name: name, Constraint: constraint}
+ if r := fn(cttp); r != nil {
+ return r
+ }
+ return cttp
+}
+
+func (cttp *ConstrainedTypeTemplateParam) GoString() string {
+ return cttp.goString(0, "")
+}
+
+func (cttp *ConstrainedTypeTemplateParam) goString(indent int, field string) string {
+ return fmt.Sprintf("%*s%sConstrainedTypeTemplateParam\n%s\n%s", indent, "", field,
+ cttp.Name.goString(indent+2, "Name: "),
+ cttp.Constraint.goString(indent+2, "Constraint: "))
}
// TemplateParamPack is a template parameter pack that appears in a
c.To.goString(indent+2, "To: "))
}
+func (c *Cast) prec() precedence {
+ return precCast
+}
+
// The parenthesize function prints the string for val, wrapped in
// parentheses if necessary.
func parenthesize(ps *printState, val AST) {
paren = true
}
if paren {
- ps.writeByte('(')
+ ps.startScope('(')
}
ps.print(val)
if paren {
- ps.writeByte(')')
+ ps.endScope(')')
}
}
}
if u.Suffix {
- parenthesize(ps, expr)
+ if ps.llvmStyle {
+ wantParens := true
+ opPrec := precUnary
+ if op != nil {
+ opPrec = op.precedence
+ }
+ if p, ok := expr.(hasPrec); ok {
+ if p.prec() < opPrec {
+ wantParens = false
+ }
+ }
+ if wantParens {
+ ps.startScope('(')
+ }
+ ps.print(expr)
+ if wantParens {
+ ps.endScope(')')
+ }
+ } else {
+ parenthesize(ps, expr)
+ }
}
if op != nil {
ps.writeByte(' ')
}
} else if c, ok := u.Op.(*Cast); ok {
- ps.writeByte('(')
+ ps.startScope('(')
ps.print(c.To)
- ps.writeByte(')')
+ ps.endScope(')')
} else {
ps.print(u.Op)
}
ps.print(expr)
} else if u.SizeofType {
// Always use parentheses for sizeof argument.
- ps.writeByte('(')
+ ps.startScope('(')
ps.print(expr)
- ps.writeByte(')')
+ ps.endScope(')')
} else if op != nil && op.Name == "__alignof__" {
// Always use parentheses for __alignof__ argument.
- ps.writeByte('(')
+ ps.startScope('(')
ps.print(expr)
- ps.writeByte(')')
+ ps.endScope(')')
} else if ps.llvmStyle {
- if op == nil || (op.Name != `operator"" ` && !isDelete) {
- ps.writeByte('(')
+ var wantParens bool
+ switch {
+ case op == nil:
+ wantParens = true
+ case op.Name == `operator"" `:
+ wantParens = false
+ case op.Name == "&":
+ wantParens = false
+ case isDelete:
+ wantParens = false
+ case op.Name == "alignof ":
+ wantParens = true
+ case op.Name == "sizeof ":
+ wantParens = true
+ case op.Name == "typeid ":
+ wantParens = true
+ default:
+ wantParens = true
+ if p, ok := expr.(hasPrec); ok {
+ if p.prec() < op.precedence {
+ wantParens = false
+ }
+ }
+ }
+ if wantParens {
+ ps.startScope('(')
}
ps.print(expr)
- if op == nil || (op.Name != `operator"" ` && !isDelete) {
- ps.writeByte(')')
+ if wantParens {
+ ps.endScope(')')
}
} else {
parenthesize(ps, expr)
u.Expr.goString(indent+2, "Expr: "))
}
+func (u *Unary) prec() precedence {
+ if p, ok := u.Op.(hasPrec); ok {
+ return p.prec()
+ }
+ return precDefault
+}
+
// isDesignatedInitializer reports whether x is a designated
// initializer.
func isDesignatedInitializer(x AST) bool {
if op != nil && strings.Contains(op.Name, "cast") {
ps.writeString(op.Name)
+
+ scopes := ps.scopes
+ ps.scopes = 0
+
ps.writeByte('<')
ps.print(b.Left)
- ps.writeString(">(")
+ ps.writeString(">")
+
+ ps.scopes = scopes
+
+ ps.startScope('(')
ps.print(b.Right)
- ps.writeByte(')')
+ ps.endScope(')')
return
}
// Use an extra set of parentheses around an expression that
// uses the greater-than operator, so that it does not get
// confused with the '>' that ends template parameters.
- if op != nil && op.Name == ">" {
- ps.writeByte('(')
+ needsOuterParen := op != nil && (op.Name == ">" || op.Name == ">>")
+ if ps.llvmStyle && ps.scopes > 0 {
+ needsOuterParen = false
+ }
+ if needsOuterParen {
+ ps.startScope('(')
}
left := b.Left
skipParens := false
- skipBothParens := false
addSpaces := ps.llvmStyle
if ps.llvmStyle && op != nil {
switch op.Name {
- case ".", "->":
- skipBothParens = true
- addSpaces = false
- case "->*":
- skipParens = true
+ case ".", "->", "->*":
addSpaces = false
}
}
}
}
- if skipParens || skipBothParens {
+ if skipParens {
ps.print(left)
} else if ps.llvmStyle {
- ps.writeByte('(')
+ prec := precPrimary
+ if p, ok := left.(hasPrec); ok {
+ prec = p.prec()
+ }
+ needsParen := false
+ if prec > b.prec() {
+ needsParen = true
+ }
+ if needsParen {
+ ps.startScope('(')
+ }
+
ps.print(left)
- ps.writeByte(')')
+
+ if needsParen {
+ ps.endScope(')')
+ }
} else {
parenthesize(ps, left)
}
if op != nil {
if op.Name != "()" {
- if addSpaces {
+ if addSpaces && op.Name != "," {
ps.writeByte(' ')
}
ps.writeString(op.Name)
ps.print(b.Op)
}
- if skipBothParens {
- ps.print(b.Right)
- } else if ps.llvmStyle {
- ps.writeByte('(')
+ if ps.llvmStyle {
+ prec := precPrimary
+ if p, ok := b.Right.(hasPrec); ok {
+ prec = p.prec()
+ }
+ needsParen := false
+ if prec >= b.prec() {
+ needsParen = true
+ }
+ if needsParen {
+ ps.startScope('(')
+ }
+
ps.print(b.Right)
- ps.writeByte(')')
+
+ if needsParen {
+ ps.endScope(')')
+ }
} else {
parenthesize(ps, b.Right)
}
- if op != nil && op.Name == ">" {
- ps.writeByte(')')
+ if needsOuterParen {
+ ps.endScope(')')
}
}
b.Right.goString(indent+2, "Right: "))
}
+func (b *Binary) prec() precedence {
+ if p, ok := b.Op.(hasPrec); ok {
+ return p.prec()
+ }
+ return precDefault
+}
+
// Trinary is the ?: trinary operation in an expression.
type Trinary struct {
Op AST
return
}
- parenthesize(ps, t.First)
+ if ps.llvmStyle {
+ wantParens := true
+ opPrec := precPrimary
+ if op, ok := t.Op.(*Operator); ok {
+ opPrec = op.precedence
+ }
+ if p, ok := t.First.(hasPrec); ok {
+ if p.prec() < opPrec {
+ wantParens = false
+ }
+ }
+ if wantParens {
+ ps.startScope('(')
+ }
+ ps.print(t.First)
+ if wantParens {
+ ps.endScope(')')
+ }
+ } else {
+ parenthesize(ps, t.First)
+ }
+
if ps.llvmStyle {
ps.writeString(" ? ")
} else {
ps.writeByte('?')
}
- parenthesize(ps, t.Second)
+
+ if ps.llvmStyle {
+ wantParens := true
+ if p, ok := t.Second.(hasPrec); ok {
+ if p.prec() < precDefault {
+ wantParens = false
+ }
+ }
+ if wantParens {
+ ps.startScope('(')
+ }
+ ps.print(t.Second)
+ if wantParens {
+ ps.endScope(')')
+ }
+ } else {
+ parenthesize(ps, t.Second)
+ }
+
ps.writeString(" : ")
- parenthesize(ps, t.Third)
+
+ if ps.llvmStyle {
+ wantParens := true
+ if p, ok := t.Third.(hasPrec); ok {
+ if p.prec() < precAssign {
+ wantParens = false
+ }
+ }
+ if wantParens {
+ ps.startScope('(')
+ }
+ ps.print(t.Third)
+ if wantParens {
+ ps.endScope(')')
+ }
+ } else {
+ parenthesize(ps, t.Third)
+ }
}
func (t *Trinary) Traverse(fn func(AST) bool) {
}
}
foldParenthesize := func(a AST) {
- if _, ok := a.(*ArgumentPack); ok || !ps.llvmStyle {
- parenthesize(ps, a)
- } else {
+ if ps.llvmStyle {
+ prec := precDefault
+ if p, ok := a.(hasPrec); ok {
+ prec = p.prec()
+ }
+ needsParen := false
+ if prec > precCast {
+ needsParen = true
+ }
+ if needsParen {
+ ps.startScope('(')
+ }
ps.print(a)
+ if needsParen {
+ ps.endScope(')')
+ }
+ } else {
+ parenthesize(ps, a)
}
}
if f.Arg2 == nil {
if f.Left {
- ps.writeString("(...")
+ ps.startScope('(')
+ ps.writeString("...")
printOp()
foldParenthesize(f.Arg1)
- ps.writeString(")")
+ ps.endScope(')')
} else {
- ps.writeString("(")
+ ps.startScope('(')
foldParenthesize(f.Arg1)
printOp()
- ps.writeString("...)")
+ ps.writeString("...")
+ ps.endScope(')')
}
} else {
- ps.writeString("(")
+ ps.startScope('(')
foldParenthesize(f.Arg1)
printOp()
ps.writeString("...")
printOp()
foldParenthesize(f.Arg2)
- ps.writeString(")")
+ ps.endScope(')')
}
}
}
func (pmc *PtrMemCast) print(ps *printState) {
- ps.writeString("(")
+ ps.startScope('(')
ps.print(pmc.Type)
ps.writeString(")(")
ps.print(pmc.Expr)
- ps.writeString(")")
+ ps.endScope(')')
}
func (pmc *PtrMemCast) Traverse(fn func(AST) bool) {
ps.writeString("true")
return
}
- } else if b.Name == "decltype(nullptr)" && l.Val == "" {
+ } else if b.Name == "decltype(nullptr)" && (l.Val == "" || l.Val == "0") {
if ps.llvmStyle {
ps.writeString("nullptr")
} else {
}
}
- ps.writeByte('(')
+ ps.startScope('(')
ps.print(l.Type)
- ps.writeByte(')')
+ ps.endScope(')')
if isFloat {
ps.writeByte('[')
indent+2, "", l.Val)
}
+func (l *Literal) prec() precedence {
+ return precPrimary
+}
+
// StringLiteral is a string literal.
type StringLiteral struct {
Type AST
}
func (el *ExprList) print(ps *printState) {
- for i, e := range el.Exprs {
- if i > 0 {
- ps.writeString(", ")
- }
- ps.print(e)
- }
+ ps.printList(el.Exprs, nil)
}
func (el *ExprList) Traverse(fn func(AST) bool) {
return s
}
+func (el *ExprList) prec() precedence {
+ return precComma
+}
+
// InitializerList is an initializer list: an optional type with a
// list of expressions.
type InitializerList struct {
// Closure is a closure, or lambda expression.
type Closure struct {
- TemplateArgs []AST
- Types []AST
- Num int
+ TemplateArgs []AST
+ TemplateArgsConstraint AST
+ Types []AST
+ Num int
+ CallConstraint AST
}
func (cl *Closure) print(ps *printState) {
func (cl *Closure) printTypes(ps *printState) {
if len(cl.TemplateArgs) > 0 {
+ scopes := ps.scopes
+ ps.scopes = 0
+
ps.writeString("<")
- for i, a := range cl.TemplateArgs {
- if i > 0 {
- ps.writeString(", ")
- }
- ps.print(a)
- }
+ ps.printList(cl.TemplateArgs, nil)
ps.writeString(">")
+
+ ps.scopes = scopes
}
- ps.writeString("(")
- for i, t := range cl.Types {
- if i > 0 {
- ps.writeString(", ")
- }
- ps.print(t)
+
+ if cl.TemplateArgsConstraint != nil {
+ ps.writeString(" requires ")
+ ps.print(cl.TemplateArgsConstraint)
+ ps.writeByte(' ')
+ }
+
+ ps.startScope('(')
+ ps.printList(cl.Types, nil)
+ ps.endScope(')')
+
+ if cl.CallConstraint != nil {
+ ps.writeString(" requires ")
+ ps.print(cl.CallConstraint)
}
- ps.writeString(")")
}
func (cl *Closure) Traverse(fn func(AST) bool) {
for _, a := range cl.TemplateArgs {
a.Traverse(fn)
}
+ if cl.TemplateArgsConstraint != nil {
+ cl.TemplateArgsConstraint.Traverse(fn)
+ }
for _, t := range cl.Types {
t.Traverse(fn)
}
+ if cl.CallConstraint != nil {
+ cl.CallConstraint.Traverse(fn)
+ }
}
}
}
}
+ var templateArgsConstraint AST
+ if cl.TemplateArgsConstraint != nil {
+ templateArgsConstraint = cl.TemplateArgsConstraint.Copy(fn, skip)
+ if templateArgsConstraint == nil {
+ templateArgsConstraint = cl.TemplateArgsConstraint
+ } else {
+ changed = true
+ }
+ }
+
types := make([]AST, len(cl.Types))
for i, t := range cl.Types {
tc := t.Copy(fn, skip)
}
}
+ var callConstraint AST
+ if cl.CallConstraint != nil {
+ callConstraint = cl.CallConstraint.Copy(fn, skip)
+ if callConstraint == nil {
+ callConstraint = cl.CallConstraint
+ } else {
+ changed = true
+ }
+ }
+
if !changed {
return fn(cl)
}
- cl = &Closure{TemplateArgs: args, Types: types, Num: cl.Num}
+ cl = &Closure{
+ TemplateArgs: args,
+ TemplateArgsConstraint: templateArgsConstraint,
+ Types: types,
+ Num: cl.Num,
+ CallConstraint: callConstraint,
+ }
if r := fn(cl); r != nil {
return r
}
}
func (cl *Closure) goString(indent int, field string) string {
- var args string
+ var args strings.Builder
if len(cl.TemplateArgs) == 0 {
- args = fmt.Sprintf("%*sTemplateArgs: nil", indent+2, "")
+ fmt.Fprintf(&args, "%*sTemplateArgs: nil", indent+2, "")
} else {
- args = fmt.Sprintf("%*sTemplateArgs:", indent+2, "")
+ fmt.Fprintf(&args, "%*sTemplateArgs:", indent+2, "")
for i, a := range cl.TemplateArgs {
- args += "\n"
- args += a.goString(indent+4, fmt.Sprintf("%d: ", i))
+ args.WriteByte('\n')
+ args.WriteString(a.goString(indent+4, fmt.Sprintf("%d: ", i)))
}
}
- var types string
+
+ var templateArgsConstraint string
+ if cl.TemplateArgsConstraint != nil {
+ templateArgsConstraint = "\n" + cl.TemplateArgsConstraint.goString(indent+2, "TemplateArgsConstraint: ")
+ }
+
+ var types strings.Builder
if len(cl.Types) == 0 {
- types = fmt.Sprintf("%*sTypes: nil", indent+2, "")
+ fmt.Fprintf(&types, "%*sTypes: nil", indent+2, "")
} else {
- types = fmt.Sprintf("%*sTypes:", indent+2, "")
+ fmt.Fprintf(&types, "%*sTypes:", indent+2, "")
for i, t := range cl.Types {
- types += "\n"
- types += t.goString(indent+4, fmt.Sprintf("%d: ", i))
+ types.WriteByte('\n')
+ types.WriteString(t.goString(indent+4, fmt.Sprintf("%d: ", i)))
}
}
- return fmt.Sprintf("%*s%sClosure: Num: %d\n%s\n%s", indent, "", field,
- cl.Num, args, types)
+
+ var callConstraint string
+ if cl.CallConstraint != nil {
+ callConstraint = "\n" + cl.CallConstraint.goString(indent+2, "CallConstraint: ")
+ }
+
+ return fmt.Sprintf("%*s%sClosure: Num: %d\n%s\n%s%s%s", indent, "", field,
+ cl.Num, args.String(), templateArgsConstraint, types.String(),
+ callConstraint)
}
// StructuredBindings is a structured binding declaration.
func (sb *StructuredBindings) print(ps *printState) {
ps.writeString("[")
- for i, b := range sb.Bindings {
- if i > 0 {
- ps.writeString(", ")
- }
- b.print(ps)
- }
+ ps.printList(sb.Bindings, nil)
ps.writeString("]")
}
func (c *Clone) print(ps *printState) {
ps.print(c.Base)
if ps.llvmStyle {
- ps.writeString(" (")
+ ps.writeByte(' ')
+ ps.startScope('(')
ps.writeString(c.Suffix)
- ps.writeByte(')')
+ ps.endScope(')')
} else {
ps.writeString(fmt.Sprintf(" [clone %s]", c.Suffix))
}
func (ei *EnableIf) print(ps *printState) {
ps.print(ei.Type)
ps.writeString(" [enable_if:")
- first := true
- for _, a := range ei.Args {
- if !first {
- ps.writeString(", ")
- }
- ps.print(a)
- first = false
- }
+ ps.printList(ei.Args, nil)
ps.writeString("]")
}
ei.Type.goString(indent+2, "Type: "), args)
}
+// ModuleName is a C++20 module.
+type ModuleName struct {
+ Parent AST
+ Name AST
+ IsPartition bool
+}
+
+func (mn *ModuleName) print(ps *printState) {
+ if mn.Parent != nil {
+ ps.print(mn.Parent)
+ }
+ if mn.IsPartition {
+ ps.writeByte(':')
+ } else if mn.Parent != nil {
+ ps.writeByte('.')
+ }
+ ps.print(mn.Name)
+}
+
+func (mn *ModuleName) Traverse(fn func(AST) bool) {
+ if fn(mn) {
+ mn.Parent.Traverse(fn)
+ mn.Name.Traverse(fn)
+ }
+}
+
+func (mn *ModuleName) Copy(fn func(AST) AST, skip func(AST) bool) AST {
+ if skip(mn) {
+ return nil
+ }
+ var parent AST
+ if mn.Parent != nil {
+ parent = mn.Parent.Copy(fn, skip)
+ }
+ name := mn.Name.Copy(fn, skip)
+ if parent == nil && name == nil {
+ return fn(mn)
+ }
+ if parent == nil {
+ parent = mn.Parent
+ }
+ if name == nil {
+ name = mn.Name
+ }
+ mn = &ModuleName{Parent: parent, Name: name, IsPartition: mn.IsPartition}
+ if r := fn(mn); r != nil {
+ return r
+ }
+ return mn
+}
+
+func (mn *ModuleName) GoString() string {
+ return mn.goString(0, "")
+}
+
+func (mn *ModuleName) goString(indent int, field string) string {
+ var parent string
+ if mn.Parent == nil {
+ parent = fmt.Sprintf("%*sParent: nil", indent+2, "")
+ } else {
+ parent = mn.Parent.goString(indent+2, "Parent: ")
+ }
+ return fmt.Sprintf("%*s%sModuleName: IsPartition: %t\n%s\n%s", indent, "", field,
+ mn.IsPartition, parent,
+ mn.Name.goString(indent+2, "Name: "))
+}
+
+// ModuleEntity is a name inside a module.
+type ModuleEntity struct {
+ Module AST
+ Name AST
+}
+
+func (me *ModuleEntity) print(ps *printState) {
+ ps.print(me.Name)
+ ps.writeByte('@')
+ ps.print(me.Module)
+}
+
+func (me *ModuleEntity) Traverse(fn func(AST) bool) {
+ if fn(me) {
+ me.Module.Traverse(fn)
+ me.Name.Traverse(fn)
+ }
+}
+
+func (me *ModuleEntity) Copy(fn func(AST) AST, skip func(AST) bool) AST {
+ if skip(me) {
+ return nil
+ }
+ module := me.Module.Copy(fn, skip)
+ name := me.Name.Copy(fn, skip)
+ if module == nil && name == nil {
+ return fn(me)
+ }
+ if module == nil {
+ module = me.Module
+ }
+ if name == nil {
+ name = me.Name
+ }
+ me = &ModuleEntity{Module: module, Name: name}
+ if r := fn(me); r != nil {
+ return r
+ }
+ return me
+}
+
+func (me *ModuleEntity) GoString() string {
+ return me.goString(0, "")
+}
+
+func (me *ModuleEntity) goString(indent int, field string) string {
+ return fmt.Sprintf("%*s%sModuleEntity:\n%s\n%s", indent, "", field,
+ me.Module.goString(indent+2, "Module: "),
+ me.Name.goString(indent+2, "Name: "))
+}
+
+// Friend is a member like friend name.
+type Friend struct {
+ Name AST
+}
+
+func (f *Friend) print(ps *printState) {
+ ps.writeString("friend ")
+ ps.print(f.Name)
+}
+
+func (f *Friend) Traverse(fn func(AST) bool) {
+ if fn(f) {
+ f.Name.Traverse(fn)
+ }
+}
+
+func (f *Friend) Copy(fn func(AST) AST, skip func(AST) bool) AST {
+ if skip(f) {
+ return nil
+ }
+ name := f.Name.Copy(fn, skip)
+ if name == nil {
+ return fn(f)
+ }
+ f = &Friend{Name: name}
+ if r := fn(f); r != nil {
+ return r
+ }
+ return f
+}
+
+func (f *Friend) GoString() string {
+ return f.goString(0, "")
+}
+
+func (f *Friend) goString(indent int, field string) string {
+ return fmt.Sprintf("%*s%sFriend:\n%s", indent, "", field,
+ f.Name.goString(indent+2, "Name: "))
+}
+
+// Constraint represents an AST with a constraint.
+type Constraint struct {
+ Name AST
+ Requires AST
+}
+
+func (c *Constraint) print(ps *printState) {
+ ps.print(c.Name)
+ ps.writeString(" requires ")
+ ps.print(c.Requires)
+}
+
+func (c *Constraint) Traverse(fn func(AST) bool) {
+ if fn(c) {
+ c.Name.Traverse(fn)
+ c.Requires.Traverse(fn)
+ }
+}
+
+func (c *Constraint) Copy(fn func(AST) AST, skip func(AST) bool) AST {
+ if skip(c) {
+ return nil
+ }
+ name := c.Name.Copy(fn, skip)
+ requires := c.Requires.Copy(fn, skip)
+ if name == nil && requires == nil {
+ return fn(c)
+ }
+ if name == nil {
+ name = c.Name
+ }
+ if requires == nil {
+ requires = c.Requires
+ }
+ c = &Constraint{Name: name, Requires: requires}
+ if r := fn(c); r != nil {
+ return r
+ }
+ return c
+}
+
+func (c *Constraint) GoString() string {
+ return c.goString(0, "")
+}
+
+func (c *Constraint) goString(indent int, field string) string {
+ return fmt.Sprintf("%*s%sConstraint:\n%s\n%s", indent, "", field,
+ c.Name.goString(indent+2, "Name: "),
+ c.Requires.goString(indent+2, "Requires: "))
+}
+
+// RequiresExpr is a C++20 requires expression.
+type RequiresExpr struct {
+ Params []AST
+ Requirements []AST
+}
+
+func (re *RequiresExpr) print(ps *printState) {
+ ps.writeString("requires")
+ if len(re.Params) > 0 {
+ ps.writeByte(' ')
+ ps.startScope('(')
+ ps.printList(re.Params, nil)
+ ps.endScope(')')
+ }
+ ps.writeByte(' ')
+ ps.startScope('{')
+ for _, req := range re.Requirements {
+ ps.print(req)
+ }
+ ps.writeByte(' ')
+ ps.endScope('}')
+}
+
+func (re *RequiresExpr) Traverse(fn func(AST) bool) {
+ if fn(re) {
+ for _, p := range re.Params {
+ p.Traverse(fn)
+ }
+ for _, r := range re.Requirements {
+ r.Traverse(fn)
+ }
+ }
+}
+
+func (re *RequiresExpr) Copy(fn func(AST) AST, skip func(AST) bool) AST {
+ if skip(re) {
+ return nil
+ }
+
+ changed := false
+
+ var params []AST
+ if len(re.Params) > 0 {
+ params = make([]AST, len(re.Params))
+ for i, p := range re.Params {
+ pc := p.Copy(fn, skip)
+ if pc == nil {
+ params[i] = p
+ } else {
+ params[i] = pc
+ changed = true
+ }
+ }
+ }
+
+ requirements := make([]AST, len(re.Requirements))
+ for i, r := range re.Requirements {
+ rc := r.Copy(fn, skip)
+ if rc == nil {
+ requirements[i] = r
+ } else {
+ requirements[i] = rc
+ changed = true
+ }
+ }
+
+ if !changed {
+ return fn(re)
+ }
+
+ re = &RequiresExpr{Params: params, Requirements: requirements}
+ if r := fn(re); r != nil {
+ return r
+ }
+ return re
+}
+
+func (re *RequiresExpr) GoString() string {
+ return re.goString(0, "")
+}
+
+func (re *RequiresExpr) goString(indent int, field string) string {
+ var params strings.Builder
+ if len(re.Params) == 0 {
+ fmt.Fprintf(¶ms, "%*sParams: nil", indent+2, "")
+ } else {
+ fmt.Fprintf(¶ms, "%*sParams:", indent+2, "")
+ for i, p := range re.Params {
+ params.WriteByte('\n')
+ params.WriteString(p.goString(indent+4, fmt.Sprintf("%d: ", i)))
+ }
+ }
+
+ var requirements strings.Builder
+ fmt.Fprintf(&requirements, "%*sRequirements:", indent+2, "")
+ for i, r := range re.Requirements {
+ requirements.WriteByte('\n')
+ requirements.WriteString(r.goString(indent+4, fmt.Sprintf("%d: ", i)))
+ }
+
+ return fmt.Sprintf("%*s%sRequirements:\n%s\n%s", indent, "", field,
+ params.String(), requirements.String())
+}
+
+// ExprRequirement is a simple requirement in a requires expression.
+// This is an arbitrary expression.
+type ExprRequirement struct {
+ Expr AST
+ Noexcept bool
+ TypeReq AST
+}
+
+func (er *ExprRequirement) print(ps *printState) {
+ ps.writeByte(' ')
+ if er.Noexcept || er.TypeReq != nil {
+ ps.startScope('{')
+ }
+ ps.print(er.Expr)
+ if er.Noexcept || er.TypeReq != nil {
+ ps.endScope('}')
+ }
+ if er.Noexcept {
+ ps.writeString(" noexcept")
+ }
+ if er.TypeReq != nil {
+ ps.writeString(" -> ")
+ ps.print(er.TypeReq)
+ }
+ ps.writeByte(';')
+}
+
+func (er *ExprRequirement) Traverse(fn func(AST) bool) {
+ if fn(er) {
+ er.Expr.Traverse(fn)
+ if er.TypeReq != nil {
+ er.TypeReq.Traverse(fn)
+ }
+ }
+}
+
+func (er *ExprRequirement) Copy(fn func(AST) AST, skip func(AST) bool) AST {
+ if skip(er) {
+ return nil
+ }
+ expr := er.Expr.Copy(fn, skip)
+ var typeReq AST
+ if er.TypeReq != nil {
+ typeReq = er.TypeReq.Copy(fn, skip)
+ }
+ if expr == nil && typeReq == nil {
+ return fn(er)
+ }
+ if expr == nil {
+ expr = er.Expr
+ }
+ if typeReq == nil {
+ typeReq = er.TypeReq
+ }
+ er = &ExprRequirement{Expr: expr, TypeReq: typeReq}
+ if r := fn(er); r != nil {
+ return r
+ }
+ return er
+}
+
+func (er *ExprRequirement) GoString() string {
+ return er.goString(0, "")
+}
+
+func (er *ExprRequirement) goString(indent int, field string) string {
+ var typeReq string
+ if er.TypeReq != nil {
+ typeReq = "\n" + er.TypeReq.goString(indent+2, "TypeReq: ")
+ }
+
+ return fmt.Sprintf("%*s%sExprRequirement: Noexcept: %t\n%s%s", indent, "", field,
+ er.Noexcept,
+ er.Expr.goString(indent+2, "Expr: "),
+ typeReq)
+}
+
+// TypeRequirement is a type requirement in a requires expression.
+type TypeRequirement struct {
+ Type AST
+}
+
+func (tr *TypeRequirement) print(ps *printState) {
+ ps.writeString(" typename ")
+ ps.print(tr.Type)
+ ps.writeByte(';')
+}
+
+func (tr *TypeRequirement) Traverse(fn func(AST) bool) {
+ if fn(tr) {
+ tr.Type.Traverse(fn)
+ }
+}
+
+func (tr *TypeRequirement) Copy(fn func(AST) AST, skip func(AST) bool) AST {
+ if skip(tr) {
+ return nil
+ }
+ typ := tr.Type.Copy(fn, skip)
+ if typ == nil {
+ return fn(tr)
+ }
+ tr = &TypeRequirement{Type: typ}
+ if r := fn(tr); r != nil {
+ return r
+ }
+ return tr
+}
+
+func (tr *TypeRequirement) GoString() string {
+ return tr.goString(0, "")
+}
+
+func (tr *TypeRequirement) goString(indent int, field string) string {
+ return fmt.Sprintf("%*s%sTypeRequirement:\n%s", indent, "", field,
+ tr.Type.goString(indent+2, ""))
+}
+
+// NestedRequirement is a nested requirement in a requires expression.
+type NestedRequirement struct {
+ Constraint AST
+}
+
+func (nr *NestedRequirement) print(ps *printState) {
+ ps.writeString(" requires ")
+ ps.print(nr.Constraint)
+ ps.writeByte(';')
+}
+
+func (nr *NestedRequirement) Traverse(fn func(AST) bool) {
+ if fn(nr) {
+ nr.Constraint.Traverse(fn)
+ }
+}
+
+func (nr *NestedRequirement) Copy(fn func(AST) AST, skip func(AST) bool) AST {
+ if skip(nr) {
+ return nil
+ }
+ constraint := nr.Constraint.Copy(fn, skip)
+ if constraint == nil {
+ return fn(nr)
+ }
+ nr = &NestedRequirement{Constraint: constraint}
+ if r := fn(nr); r != nil {
+ return r
+ }
+ return nr
+}
+
+func (nr *NestedRequirement) GoString() string {
+ return nr.goString(0, "")
+}
+
+func (nr *NestedRequirement) goString(indent int, field string) string {
+ return fmt.Sprintf("%*s%sNestedRequirement:\n%s", indent, "", field,
+ nr.Constraint.goString(indent+2, ""))
+}
+
+// ExplicitObjectParameter represents a C++23 explicit object parameter.
+type ExplicitObjectParameter struct {
+ Base AST
+}
+
+func (eop *ExplicitObjectParameter) print(ps *printState) {
+ ps.writeString("this ")
+ ps.print(eop.Base)
+}
+
+func (eop *ExplicitObjectParameter) Traverse(fn func(AST) bool) {
+ if fn(eop) {
+ eop.Base.Traverse(fn)
+ }
+}
+
+func (eop *ExplicitObjectParameter) Copy(fn func(AST) AST, skip func(AST) bool) AST {
+ if skip(eop) {
+ return nil
+ }
+ base := eop.Base.Copy(fn, skip)
+ if base == nil {
+ return fn(eop)
+ }
+ eop = &ExplicitObjectParameter{Base: base}
+ if r := fn(eop); r != nil {
+ return r
+ }
+ return eop
+}
+
+func (eop *ExplicitObjectParameter) GoString() string {
+ return eop.goString(0, "")
+}
+
+func (eop *ExplicitObjectParameter) goString(indent int, field string) string {
+ return fmt.Sprintf("%*s%sExplicitObjectParameter:\n%s", indent, "", field,
+ eop.Base.goString(indent+2, ""))
+}
+
// Print the inner types.
func (ps *printState) printInner(prefixOnly bool) []AST {
var save []AST
// a lambda, plus 1 so that 0 means not parsing a lambda.
lambdaTemplateLevel int
+ parsingConstraint bool // whether parsing a constraint expression
+
// Counts of template parameters without template arguments,
// for lambdas.
typeTemplateParamCount int
return st.specialName()
}
- a := st.name()
+ a, explicitObjectParameter := st.name()
a = simplify(a)
if !params {
enableIfArgs = st.templateArgs()
}
- ft := st.bareFunctionType(hasReturnType(a))
+ ft := st.bareFunctionType(hasReturnType(a), explicitObjectParameter)
+
+ var constraint AST
+ if len(st.str) > 0 && st.str[0] == 'Q' {
+ constraint = st.constraintExpr()
+ }
if template != nil {
st.templates = st.templates[:len(st.templates)-1]
r = &EnableIf{Type: r, Args: enableIfArgs}
}
+ if constraint != nil {
+ r = &Constraint{Name: r, Requires: constraint}
+ }
+
return r
}
//
// <unscoped-template-name> ::= <unscoped-name>
// ::= <substitution>
-func (st *state) name() AST {
+//
+// Besides the name, this returns whether it saw the code indicating
+// a C++23 explicit object parameter.
+func (st *state) name() (AST, bool) {
if len(st.str) < 1 {
st.fail("expected name")
}
+
+ var module AST
switch st.str[0] {
case 'N':
return st.nestedName()
case 'Z':
return st.localName()
case 'U':
- a, isCast := st.unqualifiedName()
+ a, isCast := st.unqualifiedName(nil)
if isCast {
st.setTemplate(a, nil)
}
- return a
+ return a, false
case 'S':
if len(st.str) < 2 {
st.advance(1)
subst := false
if st.str[1] == 't' {
st.advance(2)
- a, isCast = st.unqualifiedName()
+ a, isCast = st.unqualifiedName(nil)
a = &Qualified{Scope: &Name{Name: "std"}, Name: a, LocalName: false}
} else {
a = st.substitution(false)
+ if mn, ok := a.(*ModuleName); ok {
+ module = mn
+ break
+ }
subst = true
}
if len(st.str) > 0 && st.str[0] == 'I' {
if isCast {
st.setTemplate(a, nil)
}
- return a
+ return a, false
+ }
- default:
- a, isCast := st.unqualifiedName()
- if len(st.str) > 0 && st.str[0] == 'I' {
- st.subs.add(a)
- args := st.templateArgs()
- tmpl := &Template{Name: a, Args: args}
- if isCast {
- st.setTemplate(a, tmpl)
- st.clearTemplateArgs(args)
- isCast = false
- }
- a = tmpl
- }
+ a, isCast := st.unqualifiedName(module)
+ if len(st.str) > 0 && st.str[0] == 'I' {
+ st.subs.add(a)
+ args := st.templateArgs()
+ tmpl := &Template{Name: a, Args: args}
if isCast {
- st.setTemplate(a, nil)
+ st.setTemplate(a, tmpl)
+ st.clearTemplateArgs(args)
+ isCast = false
}
- return a
+ a = tmpl
+ }
+ if isCast {
+ st.setTemplate(a, nil)
}
+ return a, false
}
// nestedName parses:
//
// <nested-name> ::= N [<CV-qualifiers>] [<ref-qualifier>] <prefix> <unqualified-name> E
// ::= N [<CV-qualifiers>] [<ref-qualifier>] <template-prefix> <template-args> E
-func (st *state) nestedName() AST {
+//
+// Besides the name, this returns whether it saw the code indicating
+// a C++23 explicit object parameter.
+func (st *state) nestedName() (AST, bool) {
st.checkChar('N')
- q := st.cvQualifiers()
- r := st.refQualifier()
+
+ var q AST
+ var r string
+
+ explicitObjectParameter := false
+ if len(st.str) > 0 && st.str[0] == 'H' {
+ st.advance(1)
+ explicitObjectParameter = true
+ } else {
+ q = st.cvQualifiers()
+ r = st.refQualifier()
+ }
+
a := st.prefix()
+
if q != nil || r != "" {
a = &MethodWithQualifiers{Method: a, Qualifiers: q, RefQualifier: r}
}
st.fail("expected E after nested name")
}
st.advance(1)
- return a
+ return a, explicitObjectParameter
}
// prefix parses:
// The last name seen, for a constructor/destructor.
var last AST
+ var module AST
+
getLast := func(a AST) AST {
for {
if t, ok := a.(*Template); ok {
var next AST
c := st.str[0]
- if isDigit(c) || isLower(c) || c == 'U' || c == 'L' || (c == 'D' && len(st.str) > 1 && st.str[1] == 'C') {
- un, isUnCast := st.unqualifiedName()
+ if isDigit(c) || isLower(c) || c == 'U' || c == 'L' || c == 'F' || c == 'W' || (c == 'D' && len(st.str) > 1 && st.str[1] == 'C') {
+ un, isUnCast := st.unqualifiedName(module)
next = un
+ module = nil
if isUnCast {
if tn, ok := un.(*TaggedName); ok {
un = tn.Name
}
case 'S':
next = st.substitution(true)
+ if mn, ok := next.(*ModuleName); ok {
+ module = mn
+ next = nil
+ }
case 'I':
if a == nil {
st.fail("unexpected template arguments")
}
var args []AST
for len(st.str) == 0 || st.str[0] != 'E' {
- arg := st.templateArg()
+ arg := st.templateArg(nil)
args = append(args, arg)
}
st.advance(1)
st.fail("unrecognized letter in prefix")
}
}
+
+ if next == nil {
+ continue
+ }
+
last = next
if a == nil {
a = next
// ::= <local-source-name>
//
// <local-source-name> ::= L <source-name> <discriminator>
-func (st *state) unqualifiedName() (r AST, isCast bool) {
+func (st *state) unqualifiedName(module AST) (r AST, isCast bool) {
if len(st.str) < 1 {
st.fail("expected unqualified name")
}
+
+ module = st.moduleName(module)
+
+ friend := false
+ if len(st.str) > 0 && st.str[0] == 'F' {
+ st.advance(1)
+ friend = true
+ }
+
var a AST
isCast = false
c := st.str[0]
}
}
+ if module != nil {
+ a = &ModuleEntity{Module: module, Name: a}
+ }
+
if len(st.str) > 0 && st.str[0] == 'B' {
a = st.taggedName(a)
}
+ if friend {
+ a = &Friend{Name: a}
+ }
+
return a, isCast
}
return n
}
+// moduleName parses:
+//
+// <module-name> ::= <module-subname>
+// ::= <module-name> <module-subname>
+// ::= <substitution> # passed in by caller
+// <module-subname> ::= W <source-name>
+// ::= W P <source-name>
+//
+// The module name is optional. If it is not present, this returns the parent.
+func (st *state) moduleName(parent AST) AST {
+ ret := parent
+ for len(st.str) > 0 && st.str[0] == 'W' {
+ st.advance(1)
+ isPartition := false
+ if len(st.str) > 0 && st.str[0] == 'P' {
+ st.advance(1)
+ isPartition = true
+ }
+ name := st.sourceName()
+ ret = &ModuleName{
+ Parent: ret,
+ Name: name,
+ IsPartition: isPartition,
+ }
+ st.subs.add(ret)
+ }
+ return ret
+}
+
// number parses:
//
// number ::= [n] <(non-negative decimal integer)>
type operator struct {
name string
args int
+ prec precedence
}
// The operators map maps the mangled operator names to information
// about them.
var operators = map[string]operator{
- "aN": {"&=", 2},
- "aS": {"=", 2},
- "aa": {"&&", 2},
- "ad": {"&", 1},
- "an": {"&", 2},
- "at": {"alignof ", 1},
- "aw": {"co_await ", 1},
- "az": {"alignof ", 1},
- "cc": {"const_cast", 2},
- "cl": {"()", 2},
+ "aN": {"&=", 2, precAssign},
+ "aS": {"=", 2, precAssign},
+ "aa": {"&&", 2, precLogicalAnd},
+ "ad": {"&", 1, precUnary},
+ "an": {"&", 2, precAnd},
+ "at": {"alignof ", 1, precUnary},
+ "aw": {"co_await ", 1, precPrimary},
+ "az": {"alignof ", 1, precUnary},
+ "cc": {"const_cast", 2, precPostfix},
+ "cl": {"()", 2, precPostfix},
// cp is not in the ABI but is used by clang "when the call
// would use ADL except for being parenthesized."
- "cp": {"()", 2},
- "cm": {",", 2},
- "co": {"~", 1},
- "dV": {"/=", 2},
- "dX": {"[...]=", 3},
- "da": {"delete[] ", 1},
- "dc": {"dynamic_cast", 2},
- "de": {"*", 1},
- "di": {"=", 2},
- "dl": {"delete ", 1},
- "ds": {".*", 2},
- "dt": {".", 2},
- "dv": {"/", 2},
- "dx": {"]=", 2},
- "eO": {"^=", 2},
- "eo": {"^", 2},
- "eq": {"==", 2},
- "fl": {"...", 2},
- "fr": {"...", 2},
- "fL": {"...", 3},
- "fR": {"...", 3},
- "ge": {">=", 2},
- "gs": {"::", 1},
- "gt": {">", 2},
- "ix": {"[]", 2},
- "lS": {"<<=", 2},
- "le": {"<=", 2},
- "li": {`operator"" `, 1},
- "ls": {"<<", 2},
- "lt": {"<", 2},
- "mI": {"-=", 2},
- "mL": {"*=", 2},
- "mi": {"-", 2},
- "ml": {"*", 2},
- "mm": {"--", 1},
- "na": {"new[]", 3},
- "ne": {"!=", 2},
- "ng": {"-", 1},
- "nt": {"!", 1},
- "nw": {"new", 3},
- "nx": {"noexcept", 1},
- "oR": {"|=", 2},
- "oo": {"||", 2},
- "or": {"|", 2},
- "pL": {"+=", 2},
- "pl": {"+", 2},
- "pm": {"->*", 2},
- "pp": {"++", 1},
- "ps": {"+", 1},
- "pt": {"->", 2},
- "qu": {"?", 3},
- "rM": {"%=", 2},
- "rS": {">>=", 2},
- "rc": {"reinterpret_cast", 2},
- "rm": {"%", 2},
- "rs": {">>", 2},
- "sP": {"sizeof...", 1},
- "sZ": {"sizeof...", 1},
- "sc": {"static_cast", 2},
- "ss": {"<=>", 2},
- "st": {"sizeof ", 1},
- "sz": {"sizeof ", 1},
- "tr": {"throw", 0},
- "tw": {"throw ", 1},
+ "cp": {"()", 2, precPostfix},
+ "cm": {",", 2, precComma},
+ "co": {"~", 1, precUnary},
+ "dV": {"/=", 2, precAssign},
+ "dX": {"[...]=", 3, precAssign},
+ "da": {"delete[] ", 1, precUnary},
+ "dc": {"dynamic_cast", 2, precPostfix},
+ "de": {"*", 1, precUnary},
+ "di": {"=", 2, precAssign},
+ "dl": {"delete ", 1, precUnary},
+ "ds": {".*", 2, precPtrMem},
+ "dt": {".", 2, precPostfix},
+ "dv": {"/", 2, precAssign},
+ "dx": {"]=", 2, precAssign},
+ "eO": {"^=", 2, precAssign},
+ "eo": {"^", 2, precXor},
+ "eq": {"==", 2, precEqual},
+ "fl": {"...", 2, precPrimary},
+ "fr": {"...", 2, precPrimary},
+ "fL": {"...", 3, precPrimary},
+ "fR": {"...", 3, precPrimary},
+ "ge": {">=", 2, precRel},
+ "gs": {"::", 1, precUnary},
+ "gt": {">", 2, precRel},
+ "ix": {"[]", 2, precPostfix},
+ "lS": {"<<=", 2, precAssign},
+ "le": {"<=", 2, precRel},
+ "li": {`operator"" `, 1, precUnary},
+ "ls": {"<<", 2, precShift},
+ "lt": {"<", 2, precRel},
+ "mI": {"-=", 2, precAssign},
+ "mL": {"*=", 2, precAssign},
+ "mi": {"-", 2, precAdd},
+ "ml": {"*", 2, precMul},
+ "mm": {"--", 1, precPostfix},
+ "na": {"new[]", 3, precUnary},
+ "ne": {"!=", 2, precEqual},
+ "ng": {"-", 1, precUnary},
+ "nt": {"!", 1, precUnary},
+ "nw": {"new", 3, precUnary},
+ "nx": {"noexcept", 1, precUnary},
+ "oR": {"|=", 2, precAssign},
+ "oo": {"||", 2, precLogicalOr},
+ "or": {"|", 2, precOr},
+ "pL": {"+=", 2, precAssign},
+ "pl": {"+", 2, precAdd},
+ "pm": {"->*", 2, precPtrMem},
+ "pp": {"++", 1, precPostfix},
+ "ps": {"+", 1, precUnary},
+ "pt": {"->", 2, precPostfix},
+ "qu": {"?", 3, precCond},
+ "rM": {"%=", 2, precAssign},
+ "rS": {">>=", 2, precAssign},
+ "rc": {"reinterpret_cast", 2, precPostfix},
+ "rm": {"%", 2, precMul},
+ "rs": {">>", 2, precShift},
+ "sP": {"sizeof...", 1, precUnary},
+ "sZ": {"sizeof...", 1, precUnary},
+ "sc": {"static_cast", 2, precPostfix},
+ "ss": {"<=>", 2, precSpaceship},
+ "st": {"sizeof ", 1, precUnary},
+ "sz": {"sizeof ", 1, precUnary},
+ "te": {"typeid ", 1, precPostfix},
+ "ti": {"typeid ", 1, precPostfix},
+ "tr": {"throw", 0, precPrimary},
+ "tw": {"throw ", 1, precUnary},
}
// operatorName parses:
return &Cast{To: t}, 1
} else if op, ok := operators[code]; ok {
- return &Operator{Name: op.name}, op.args
+ return &Operator{Name: op.name, precedence: op.prec}, op.args
} else {
st.failEarlier("unrecognized operator code", 2)
panic("not reached")
// <local-name> ::= Z <(function) encoding> E <(entity) name> [<discriminator>]
// ::= Z <(function) encoding> E s [<discriminator>]
// ::= Z <(function) encoding> E d [<parameter> number>] _ <entity name>
-func (st *state) localName() AST {
+//
+// Besides the name, this returns whether it saw the code indicating
+// a C++23 explicit object parameter.
+func (st *state) localName() (AST, bool) {
st.checkChar('Z')
fn := st.encoding(true, forLocalName)
if len(st.str) == 0 || st.str[0] != 'E' {
st.advance(1)
var n AST = &Name{Name: "string literal"}
n = st.discriminator(n)
- return &Qualified{Scope: fn, Name: n, LocalName: true}
+ return &Qualified{Scope: fn, Name: n, LocalName: true}, false
} else {
num := -1
if len(st.str) > 0 && st.str[0] == 'd' {
st.advance(1)
num = st.compactNumber()
}
- n := st.name()
+ n, explicitObjectParameter := st.name()
n = st.discriminator(n)
if num >= 0 {
n = &DefaultArg{Num: num, Arg: n}
}
- return &Qualified{Scope: fn, Name: n, LocalName: true}
+ return &Qualified{Scope: fn, Name: n, LocalName: true}, explicitObjectParameter
}
}
// ::= Gr <resource name>
// ::= GTt <encoding>
// ::= GTn <encoding>
+// ::= GI <module name>
func (st *state) specialName() AST {
if st.str[0] == 'T' {
st.advance(1)
t := st.demangleType(false)
return &Special{Prefix: "typeinfo name for ", Val: t}
case 'A':
- t := st.templateArg()
+ t := st.templateArg(nil)
return &Special{Prefix: "template parameter object for ", Val: t}
case 'h':
st.callOffset('h')
t := st.demangleType(false)
return &Special{Prefix: "java Class for ", Val: t}
case 'H':
- n := st.name()
+ n, _ := st.name()
return &Special{Prefix: "TLS init function for ", Val: n}
case 'W':
- n := st.name()
+ n, _ := st.name()
return &Special{Prefix: "TLS wrapper function for ", Val: n}
default:
st.fail("unrecognized special T name code")
st.advance(1)
switch c {
case 'V':
- n := st.name()
+ n, _ := st.name()
return &Special{Prefix: "guard variable for ", Val: n}
case 'R':
- n := st.name()
+ n, _ := st.name()
st.seqID(true)
return &Special{Prefix: "reference temporary for ", Val: n}
case 'A':
}
case 'r':
return st.javaResource()
+ case 'I':
+ module := st.moduleName(nil)
+ if module == nil {
+ st.fail("expected module after GI")
+ }
+ return &Special{Prefix: "initializer for module ", Val: module}
default:
st.fail("unrecognized special G name code")
panic("not reached")
case 'u':
st.advance(1)
ret = st.sourceName()
+ if len(st.str) > 0 && st.str[0] == 'I' {
+ st.advance(1)
+ base := st.demangleType(false)
+ if len(st.str) == 0 || st.str[0] != 'E' {
+ st.fail("expected E after transformed type")
+ }
+ st.advance(1)
+ ret = &TransformedType{Name: ret.(*Name).Name, Base: base}
+ }
case 'F':
ret = st.functionType()
- case 'N', 'Z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
- ret = st.name()
+ case 'N', 'W', 'Z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
+ ret, _ = st.name()
case 'A':
ret = st.arrayType(isCast)
case 'M':
if len(st.str) > 1 && (st.str[1] == 's' || st.str[1] == 'u' || st.str[1] == 'e') {
c = st.str[1]
st.advance(2)
- ret = st.name()
+ ret, _ = st.name()
var kind string
switch c {
case 's':
}
if isDigit(c2) || c2 == '_' || isUpper(c2) {
ret = st.substitution(false)
+ if _, ok := ret.(*ModuleName); ok {
+ ret, _ = st.unqualifiedName(ret)
+ st.subs.add(ret)
+ }
if len(st.str) == 0 || st.str[0] != 'I' {
addSubst = false
} else {
}
}
} else {
- ret = st.name()
+ ret, _ = st.name()
// This substitution is not itself a
// substitution candidate, unless template
// arguments were added.
ret = st.vectorType(isCast)
addSubst = true
+ case 'B', 'U':
+ signed := c2 == 'B'
+ var size AST
+ if len(st.str) > 0 && isDigit(st.str[0]) {
+ bits := st.number()
+ size = &Name{Name: fmt.Sprintf("%d", bits)}
+ } else {
+ size = st.expression()
+ }
+ if len(st.str) == 0 || st.str[0] != '_' {
+ st.fail("expected _ after _BitInt size")
+ }
+ st.advance(1)
+ ret = &BitIntType{Size: size, Signed: signed}
+
+ case 'k':
+ constraint, _ := st.name()
+ ret = &SuffixType{
+ Base: constraint,
+ Suffix: "auto",
+ }
+
+ case 'K':
+ constraint, _ := st.name()
+ ret = &SuffixType{
+ Base: constraint,
+ Suffix: "decltype(auto)",
+ }
+
default:
st.fail("unrecognized D code in type")
}
qual = &Qualifier{Name: "noexcept", Exprs: []AST{expr}}
case 'w':
st.advance(2)
- parmlist := st.parmlist()
+ parmlist := st.parmlist(false)
if len(st.str) == 0 || st.str[0] != 'E' {
st.fail("expected E after throw parameter list")
}
// parmlist parses:
//
// <type>+
-func (st *state) parmlist() []AST {
+func (st *state) parmlist(explicitObjectParameter bool) []AST {
var ret []AST
for {
if len(st.str) < 1 {
// This is a function ref-qualifier.
break
}
+ if st.str[0] == 'Q' {
+ // This is a requires clause.
+ break
+ }
ptype := st.demangleType(false)
+
+ if len(ret) == 0 && explicitObjectParameter {
+ ptype = &ExplicitObjectParameter{Base: ptype}
+ }
+
ret = append(ret, ptype)
}
// Function has C linkage. We don't print this.
st.advance(1)
}
- ret := st.bareFunctionType(true)
+ ret := st.bareFunctionType(true, false)
r := st.refQualifier()
if r != "" {
ret = &MethodWithQualifiers{Method: ret, Qualifiers: nil, RefQualifier: r}
// bareFunctionType parses:
//
// <bare-function-type> ::= [J]<type>+
-func (st *state) bareFunctionType(hasReturnType bool) AST {
+func (st *state) bareFunctionType(hasReturnType, explicitObjectParameter bool) AST {
if len(st.str) > 0 && st.str[0] == 'J' {
hasReturnType = true
st.advance(1)
if hasReturnType {
returnType = st.demangleType(false)
}
- types := st.parmlist()
+ types := st.parmlist(explicitObjectParameter)
return &FunctionType{
Return: returnType,
Args: types,
// this out in substitution and simplify.
func (st *state) templateParam() AST {
off := st.off
+ str := st.str
st.checkChar('T')
level := 0
n := st.compactNumber()
+ // We don't try to substitute template parameters in a
+ // constraint expression.
+ if st.parsingConstraint {
+ return &Name{Name: str[:st.off-1-off]}
+ }
+
if level >= len(st.templates) {
if st.lambdaTemplateLevel > 0 && level == st.lambdaTemplateLevel-1 {
// Lambda auto params are mangled as template params.
// This handles the forward referencing template parameters found in
// cast operators.
func (st *state) setTemplate(a AST, tmpl *Template) {
- var seen []AST
+ seen := make(map[AST]bool)
a.Traverse(func(a AST) bool {
switch a := a.(type) {
case *TemplateParam:
// https://gcc.gnu.org/PR78252.
return false
default:
- for _, v := range seen {
- if v == a {
- return false
- }
+ if seen[a] {
+ return false
}
- seen = append(seen, a)
+ seen[a] = true
return true
}
})
var ret []AST
for len(st.str) == 0 || st.str[0] != 'E' {
- arg := st.templateArg()
+ arg := st.templateArg(ret)
ret = append(ret, arg)
+
+ if len(st.str) > 0 && st.str[0] == 'Q' {
+ // A list of template arguments can have a
+ // constraint, but we don't demangle it.
+ st.constraintExpr()
+ if len(st.str) == 0 || st.str[0] != 'E' {
+ st.fail("expected end of template arguments after constraint")
+ }
+ }
}
st.advance(1)
return ret
// <template-arg> ::= <type>
// ::= X <expression> E
// ::= <expr-primary>
-func (st *state) templateArg() AST {
+// ::= J <template-arg>* E
+// ::= LZ <encoding> E
+// ::= <template-param-decl> <template-arg>
+func (st *state) templateArg(prev []AST) AST {
if len(st.str) == 0 {
st.fail("missing template argument")
}
args := st.templateArgs()
return &ArgumentPack{Args: args}
+ case 'T':
+ var arg byte
+ if len(st.str) > 1 {
+ arg = st.str[1]
+ }
+ switch arg {
+ case 'y', 'n', 't', 'p', 'k':
+ off := st.off
+
+ // Apparently template references in the
+ // template parameter refer to previous
+ // arguments in the same template.
+ template := &Template{Args: prev}
+ st.templates = append(st.templates, template)
+
+ param, _ := st.templateParamDecl()
+
+ st.templates = st.templates[:len(st.templates)-1]
+
+ if param == nil {
+ st.failEarlier("expected template parameter as template argument", st.off-off)
+ }
+ arg := st.templateArg(nil)
+ return &TemplateParamQualifiedArg{Param: param, Arg: arg}
+ }
+ return st.demangleType(false)
+
default:
return st.demangleType(false)
}
st.advance(2)
var args []AST
for len(st.str) == 0 || st.str[0] != 'E' {
- arg := st.templateArg()
+ arg := st.templateArg(nil)
args = append(args, arg)
}
st.advance(1)
// Skip operator function ID.
st.advance(2)
}
- n, _ := st.unqualifiedName()
+ n, _ := st.unqualifiedName(nil)
if len(st.str) > 0 && st.str[0] == 'I' {
args := st.templateArgs()
n = &Template{Name: n, Args: args}
st.advance(1)
break
}
- arg := st.templateArg()
+ arg := st.templateArg(nil)
args = append(args, arg)
}
return &Binary{
Left: name,
Right: &ExprList{Exprs: args},
}
+ } else if st.str[0] == 'r' && len(st.str) > 1 && (st.str[1] == 'q' || st.str[1] == 'Q') {
+ return st.requiresExpr()
} else {
if len(st.str) < 2 {
st.fail("missing operator code")
right = st.expression()
return &Fold{Left: code[1] == 'l', Op: left, Arg1: right, Arg2: nil}
} else if code == "di" {
- left, _ = st.unqualifiedName()
+ left, _ = st.unqualifiedName(nil)
} else {
left = st.expression()
}
if code == "cl" || code == "cp" {
right = st.exprList('E')
} else if code == "dt" || code == "pt" {
- right = st.unresolvedName()
- if len(st.str) > 0 && st.str[0] == 'I' {
- args := st.templateArgs()
- right = &Template{Name: right, Args: args}
+ if len(st.str) > 0 && st.str[0] == 'L' {
+ right = st.exprPrimary()
+ } else {
+ right = st.unresolvedName()
+ if len(st.str) > 0 && st.str[0] == 'I' {
+ args := st.templateArgs()
+ right = &Template{Name: right, Args: args}
+ }
}
} else {
right = st.expression()
} else {
s = &Qualified{Scope: s, Name: n, LocalName: false}
}
- st.subs.add(s)
}
if s == nil {
st.fail("missing scope in unresolved name")
return n
}
+// requiresExpr parses:
+//
+// <expression> ::= rQ <bare-function-type> _ <requirement>+ E
+// ::= rq <requirement>+ E
+// <requirement> ::= X <expression> [N] [R <type-constraint>]
+// ::= T <type>
+// ::= Q <constraint-expression>
+func (st *state) requiresExpr() AST {
+ st.checkChar('r')
+ if len(st.str) == 0 || (st.str[0] != 'q' && st.str[0] != 'Q') {
+ st.fail("expected q or Q in requires clause in expression")
+ }
+ kind := st.str[0]
+ st.advance(1)
+
+ var params []AST
+ if kind == 'Q' {
+ for len(st.str) > 0 && st.str[0] != '_' {
+ typ := st.demangleType(false)
+ params = append(params, typ)
+ }
+ st.advance(1)
+ }
+
+ var requirements []AST
+ for len(st.str) > 0 && st.str[0] != 'E' {
+ var req AST
+ switch st.str[0] {
+ case 'X':
+ st.advance(1)
+ expr := st.expression()
+ var noexcept bool
+ if len(st.str) > 0 && st.str[0] == 'N' {
+ st.advance(1)
+ noexcept = true
+ }
+ var typeReq AST
+ if len(st.str) > 0 && st.str[0] == 'R' {
+ st.advance(1)
+ typeReq, _ = st.name()
+ }
+ req = &ExprRequirement{
+ Expr: expr,
+ Noexcept: noexcept,
+ TypeReq: typeReq,
+ }
+
+ case 'T':
+ st.advance(1)
+ typ := st.demangleType(false)
+ req = &TypeRequirement{Type: typ}
+
+ case 'Q':
+ st.advance(1)
+ // We parse a regular expression rather than a
+ // constraint expression.
+ expr := st.expression()
+ req = &NestedRequirement{Constraint: expr}
+
+ default:
+ st.fail("unrecognized requirement code")
+ }
+
+ requirements = append(requirements, req)
+ }
+
+ if len(st.str) == 0 || st.str[0] != 'E' {
+ st.fail("expected E after requirements")
+ }
+ st.advance(1)
+
+ return &RequiresExpr{
+ Params: params,
+ Requirements: requirements,
+ }
+}
+
// exprPrimary parses:
//
// <expr-primary> ::= L <type> <(value) number> E
template.Args = append(template.Args, templateVal)
}
- types := st.parmlist()
+ var templateArgsConstraint AST
+ if len(st.str) > 0 && st.str[0] == 'Q' {
+ templateArgsConstraint = st.constraintExpr()
+ }
+
+ types := st.parmlist(false)
st.lambdaTemplateLevel = oldLambdaTemplateLevel
st.templates = st.templates[:len(st.templates)-1]
}
+ var callConstraint AST
+ if len(st.str) > 0 && st.str[0] == 'Q' {
+ callConstraint = st.constraintExpr()
+ }
+
if len(st.str) == 0 || st.str[0] != 'E' {
st.fail("expected E after closure type name")
}
st.advance(1)
num := st.compactNumber()
- return &Closure{TemplateArgs: templateArgs, Types: types, Num: num}
+ return &Closure{
+ TemplateArgs: templateArgs,
+ TemplateArgsConstraint: templateArgsConstraint,
+ Types: types,
+ Num: num,
+ CallConstraint: callConstraint,
+ }
}
// templateParamDecl parses:
Name: name,
}
return tp, name
+ case 'k':
+ st.advance(2)
+ constraint, _ := st.name()
+ name := mk("$T", &st.typeTemplateParamCount)
+ tp := &ConstrainedTypeTemplateParam{
+ Name: name,
+ Constraint: constraint,
+ }
+ return tp, name
case 'n':
st.advance(2)
name := mk("$N", &st.nonTypeTemplateParamCount)
name := mk("$TT", &st.templateTemplateParamCount)
var params []AST
var template *Template
+ var constraint AST
for {
if len(st.str) == 0 {
st.fail("expected closure template parameter")
st.templates = append(st.templates, template)
}
template.Args = append(template.Args, templateVal)
+
+ if len(st.str) > 0 && st.str[0] == 'Q' {
+ // A list of template template
+ // parameters can have a constraint.
+ constraint = st.constraintExpr()
+ if len(st.str) == 0 || st.str[0] != 'E' {
+ st.fail("expected end of template template parameters after constraint")
+ }
+ }
}
if template != nil {
st.templates = st.templates[:len(st.templates)-1]
}
tp := &TemplateTemplateParam{
- Name: name,
- Params: params,
+ Name: name,
+ Params: params,
+ Constraint: constraint,
}
return tp, name
case 'p':
return ret
}
+// constraintExpr parses a constraint expression. This is just a
+// regular expression, but template parameters are handled specially.
+func (st *state) constraintExpr() AST {
+ st.checkChar('Q')
+
+ hold := st.parsingConstraint
+ st.parsingConstraint = true
+ defer func() { st.parsingConstraint = hold }()
+
+ return st.expression()
+}
+
// Recognize a clone suffix. These are not part of the mangling API,
// but are added by GCC when cloning functions.
func (st *state) cloneSuffix(a AST) AST {
default:
return nil
}
+ if st.parsingConstraint {
+ // We don't try to substitute template
+ // parameters in a constraint expression.
+ return &Name{Name: fmt.Sprintf("T%d", index)}
+ }
if st.lambdaTemplateLevel > 0 {
if _, ok := a.(*LambdaAuto); ok {
return nil
return &TemplateParam{Index: index, Template: template}
}
- var seen []AST
+ seen := make(map[AST]bool)
skip := func(a AST) bool {
switch a := a.(type) {
case *Typed:
case *TemplateParam, *LambdaAuto:
return false
}
- for _, v := range seen {
- if v == a {
- return true
- }
+ if seen[a] {
+ return true
}
- seen = append(seen, a)
+ seen[a] = true
return false
}
// simplify replaces template parameters with their expansions, and
// merges qualifiers.
func simplify(a AST) AST {
- var seen []AST
+ seen := make(map[AST]bool)
skip := func(a AST) bool {
- for _, v := range seen {
- if v == a {
- return true
- }
+ if seen[a] {
+ return true
}
- seen = append(seen, a)
+ seen[a] = true
return false
}
if r := a.Copy(simplifyOne, skip); r != nil {
return nil
}
- var seen []AST
+ seen := make(map[AST]bool)
skip := func(sub AST) bool {
// Don't traverse into another
// pack expansion.
if _, ok := sub.(*PackExpansion); ok {
return true
}
- for _, v := range seen {
- if v == sub {
- return true
- }
+ if seen[sub] {
+ return true
}
- seen = append(seen, sub)
+ seen[sub] = true
return false
}
// findArgumentPack walks the AST looking for the argument pack for a
// pack expansion. We find it via a template parameter.
func (st *state) findArgumentPack(a AST) *ArgumentPack {
- var seen []AST
+ seen := make(map[AST]bool)
var ret *ArgumentPack
a.Traverse(func(a AST) bool {
if ret != nil {
case *UnnamedType, *FixedType, *DefaultArg:
return false
}
- for _, v := range seen {
- if v == a {
- return false
- }
+ if seen[a] {
+ return false
}
- seen = append(seen, a)
+ seen[a] = true
return true
})
return ret
-# github.com/google/pprof v0.0.0-20240207164012-fb44976bdcd5
+# github.com/google/pprof v0.0.0-20240528025155-186aa0362fba
## explicit; go 1.19
github.com/google/pprof/driver
github.com/google/pprof/internal/binutils
github.com/google/pprof/internal/transport
github.com/google/pprof/profile
github.com/google/pprof/third_party/svgpan
-# github.com/ianlancetaylor/demangle v0.0.0-20230524184225-eabc099b10ab
+# github.com/ianlancetaylor/demangle v0.0.0-20240312041847-bd984b5ce465
## explicit; go 1.13
github.com/ianlancetaylor/demangle
# golang.org/x/arch v0.7.0