diff options
| author | Dmitri Shuralyov <dmitshur@golang.org> | 2022-05-16 18:44:48 -0400 |
|---|---|---|
| committer | Gopher Robot <gobot@golang.org> | 2022-05-17 14:39:18 +0000 |
| commit | cf7ec0fa098a46c3b75cc3d625f5d7528fe6e984 (patch) | |
| tree | f7ccf0aeef546b1889fb6185723e88f851db3fc2 /src/cmd/vendor | |
| parent | 41b9d8c75e45636a153c2a31d117196a22a7fc6c (diff) | |
| download | go-cf7ec0fa098a46c3b75cc3d625f5d7528fe6e984.tar.xz | |
cmd/pprof: update vendored github.com/google/pprof
Pull in the latest published version of github.com/google/pprof
as part of go.dev/issue/36905.
Done with:
go get github.com/google/pprof@upgrade
go mod tidy
go mod vendor
For #36905.
Change-Id: I3c8279fce2f20cb940a4e46b2b850703e1fc7964
Reviewed-on: https://go-review.googlesource.com/c/go/+/406359
TryBot-Result: Gopher Robot <gobot@golang.org>
Auto-Submit: Dmitri Shuralyov <dmitshur@golang.org>
Reviewed-by: Dmitri Shuralyov <dmitshur@google.com>
Reviewed-by: Cherry Mui <cherryyz@google.com>
Run-TryBot: Dmitri Shuralyov <dmitshur@golang.org>
Diffstat (limited to 'src/cmd/vendor')
17 files changed, 1916 insertions, 1716 deletions
diff --git a/src/cmd/vendor/github.com/google/pprof/internal/driver/html/common.css b/src/cmd/vendor/github.com/google/pprof/internal/driver/html/common.css new file mode 100644 index 0000000000..03755abc0e --- /dev/null +++ b/src/cmd/vendor/github.com/google/pprof/internal/driver/html/common.css @@ -0,0 +1,272 @@ +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} +html, body { + height: 100%; +} +body { + font-family: 'Roboto', -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; + font-size: 13px; + line-height: 1.4; + display: flex; + flex-direction: column; +} +a { + color: #2a66d9; +} +.header { + display: flex; + align-items: center; + height: 44px; + min-height: 44px; + background-color: #eee; + color: #212121; + padding: 0 1rem; +} +.header > div { + margin: 0 0.125em; +} +.header .title h1 { + font-size: 1.75em; + margin-right: 1rem; + margin-bottom: 4px; +} +.header .title a { + color: #212121; + text-decoration: none; +} +.header .title a:hover { + text-decoration: underline; +} +.header .description { + width: 100%; + text-align: right; + white-space: nowrap; +} +@media screen and (max-width: 799px) { + .header input { + display: none; + } +} +#detailsbox { + display: none; + z-index: 1; + position: fixed; + top: 40px; + right: 20px; + background-color: #ffffff; + box-shadow: 0 1px 5px rgba(0,0,0,.3); + line-height: 24px; + padding: 1em; + text-align: left; +} +.header input { + background: white url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' style='pointer-events:none;display:block;width:100%25;height:100%25;fill:%23757575'%3E%3Cpath d='M15.5 14h-.79l-.28-.27C15.41 12.59 16 11.11 16 9.5 16 5.91 13.09 3 9.5 3S3 5.91 3 9.5 5.91 16 9.5 16c1.61.0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z'/%3E%3C/svg%3E") no-repeat 4px center/20px 20px; + border: 1px solid #d1d2d3; + border-radius: 2px 0 0 2px; + padding: 0.25em; + padding-left: 28px; + margin-left: 1em; + font-family: 'Roboto', 'Noto', sans-serif; + font-size: 1em; + line-height: 24px; + color: #212121; +} +.downArrow { + border-top: .36em solid #ccc; + border-left: .36em solid transparent; + border-right: .36em solid transparent; + margin-bottom: .05em; + margin-left: .5em; + transition: border-top-color 200ms; +} +.menu-item { + height: 100%; + text-transform: uppercase; + font-family: 'Roboto Medium', -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; + position: relative; +} +.menu-item .menu-name:hover { + opacity: 0.75; +} +.menu-item .menu-name:hover .downArrow { + border-top-color: #666; +} +.menu-name { + height: 100%; + padding: 0 0.5em; + display: flex; + align-items: center; + justify-content: center; +} +.menu-name a { + text-decoration: none; + color: #212121; +} +.submenu { + display: none; + z-index: 1; + margin-top: -4px; + min-width: 10em; + position: absolute; + left: 0px; + background-color: white; + box-shadow: 0 1px 5px rgba(0,0,0,.3); + font-size: 100%; + text-transform: none; +} +.menu-item, .submenu { + user-select: none; + -moz-user-select: none; + -ms-user-select: none; + -webkit-user-select: none; +} +.submenu hr { + border: 0; + border-top: 2px solid #eee; +} +.submenu a { + display: block; + padding: .5em 1em; + text-decoration: none; +} +.submenu a:hover, .submenu a.active { + color: white; + background-color: #6b82d6; +} +.submenu a.disabled { + color: gray; + pointer-events: none; +} +.menu-check-mark { + position: absolute; + left: 2px; +} +.menu-delete-btn { + position: absolute; + right: 2px; +} + +{{/* Used to disable events when a modal dialog is displayed */}} +#dialog-overlay { + display: none; + position: fixed; + left: 0px; + top: 0px; + width: 100%; + height: 100%; + background-color: rgba(1,1,1,0.1); +} + +.dialog { + {{/* Displayed centered horizontally near the top */}} + display: none; + position: fixed; + margin: 0px; + top: 60px; + left: 50%; + transform: translateX(-50%); + + z-index: 3; + font-size: 125%; + background-color: #ffffff; + box-shadow: 0 1px 5px rgba(0,0,0,.3); +} +.dialog-header { + font-size: 120%; + border-bottom: 1px solid #CCCCCC; + width: 100%; + text-align: center; + background: #EEEEEE; + user-select: none; +} +.dialog-footer { + border-top: 1px solid #CCCCCC; + width: 100%; + text-align: right; + padding: 10px; +} +.dialog-error { + margin: 10px; + color: red; +} +.dialog input { + margin: 10px; + font-size: inherit; +} +.dialog button { + margin-left: 10px; + font-size: inherit; +} +#save-dialog, #delete-dialog { + width: 50%; + max-width: 20em; +} +#delete-prompt { + padding: 10px; +} + +#content { + overflow-y: scroll; + padding: 1em; +} +#top { + overflow-y: scroll; +} +#graph { + overflow: hidden; +} +#graph svg { + width: 100%; + height: auto; + padding: 10px; +} +#content.source .filename { + margin-top: 0; + margin-bottom: 1em; + font-size: 120%; +} +#content.source pre { + margin-bottom: 3em; +} +table { + border-spacing: 0px; + width: 100%; + padding-bottom: 1em; + white-space: nowrap; +} +table thead { + font-family: 'Roboto Medium', -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; +} +table tr th { + position: sticky; + top: 0; + background-color: #ddd; + text-align: right; + padding: .3em .5em; +} +table tr td { + padding: .3em .5em; + text-align: right; +} +#top table tr th:nth-child(6), +#top table tr th:nth-child(7), +#top table tr td:nth-child(6), +#top table tr td:nth-child(7) { + text-align: left; +} +#top table tr td:nth-child(6) { + width: 100%; + text-overflow: ellipsis; + overflow: hidden; + white-space: nowrap; +} +#flathdr1, #flathdr2, #cumhdr1, #cumhdr2, #namehdr { + cursor: ns-resize; +} +.hilite { + background-color: #ebf5fb; + font-weight: bold; +} diff --git a/src/cmd/vendor/github.com/google/pprof/internal/driver/html/common.js b/src/cmd/vendor/github.com/google/pprof/internal/driver/html/common.js new file mode 100644 index 0000000000..4fe3caa442 --- /dev/null +++ b/src/cmd/vendor/github.com/google/pprof/internal/driver/html/common.js @@ -0,0 +1,693 @@ +// Make svg pannable and zoomable. +// Call clickHandler(t) if a click event is caught by the pan event handlers. +function initPanAndZoom(svg, clickHandler) { + 'use strict'; + + // Current mouse/touch handling mode + const IDLE = 0; + const MOUSEPAN = 1; + const TOUCHPAN = 2; + const TOUCHZOOM = 3; + let mode = IDLE; + + // State needed to implement zooming. + let currentScale = 1.0; + const initWidth = svg.viewBox.baseVal.width; + const initHeight = svg.viewBox.baseVal.height; + + // State needed to implement panning. + let panLastX = 0; // Last event X coordinate + let panLastY = 0; // Last event Y coordinate + let moved = false; // Have we seen significant movement + let touchid = null; // Current touch identifier + + // State needed for pinch zooming + let touchid2 = null; // Second id for pinch zooming + let initGap = 1.0; // Starting gap between two touches + let initScale = 1.0; // currentScale when pinch zoom started + let centerPoint = null; // Center point for scaling + + // Convert event coordinates to svg coordinates. + function toSvg(x, y) { + const p = svg.createSVGPoint(); + p.x = x; + p.y = y; + let m = svg.getCTM(); + if (m == null) m = svg.getScreenCTM(); // Firefox workaround. + return p.matrixTransform(m.inverse()); + } + + // Change the scaling for the svg to s, keeping the point denoted + // by u (in svg coordinates]) fixed at the same screen location. + function rescale(s, u) { + // Limit to a good range. + if (s < 0.2) s = 0.2; + if (s > 10.0) s = 10.0; + + currentScale = s; + + // svg.viewBox defines the visible portion of the user coordinate + // system. So to magnify by s, divide the visible portion by s, + // which will then be stretched to fit the viewport. + const vb = svg.viewBox; + const w1 = vb.baseVal.width; + const w2 = initWidth / s; + const h1 = vb.baseVal.height; + const h2 = initHeight / s; + vb.baseVal.width = w2; + vb.baseVal.height = h2; + + // We also want to adjust vb.baseVal.x so that u.x remains at same + // screen X coordinate. In other words, want to change it from x1 to x2 + // so that: + // (u.x - x1) / w1 = (u.x - x2) / w2 + // Simplifying that, we get + // (u.x - x1) * (w2 / w1) = u.x - x2 + // x2 = u.x - (u.x - x1) * (w2 / w1) + vb.baseVal.x = u.x - (u.x - vb.baseVal.x) * (w2 / w1); + vb.baseVal.y = u.y - (u.y - vb.baseVal.y) * (h2 / h1); + } + + function handleWheel(e) { + if (e.deltaY == 0) return; + // Change scale factor by 1.1 or 1/1.1 + rescale(currentScale * (e.deltaY < 0 ? 1.1 : (1/1.1)), + toSvg(e.offsetX, e.offsetY)); + } + + function setMode(m) { + mode = m; + touchid = null; + touchid2 = null; + } + + function panStart(x, y) { + moved = false; + panLastX = x; + panLastY = y; + } + + function panMove(x, y) { + let dx = x - panLastX; + let dy = y - panLastY; + if (Math.abs(dx) <= 2 && Math.abs(dy) <= 2) return; // Ignore tiny moves + + moved = true; + panLastX = x; + panLastY = y; + + // Firefox workaround: get dimensions from parentNode. + const swidth = svg.clientWidth || svg.parentNode.clientWidth; + const sheight = svg.clientHeight || svg.parentNode.clientHeight; + + // Convert deltas from screen space to svg space. + dx *= (svg.viewBox.baseVal.width / swidth); + dy *= (svg.viewBox.baseVal.height / sheight); + + svg.viewBox.baseVal.x -= dx; + svg.viewBox.baseVal.y -= dy; + } + + function handleScanStart(e) { + if (e.button != 0) return; // Do not catch right-clicks etc. + setMode(MOUSEPAN); + panStart(e.clientX, e.clientY); + e.preventDefault(); + svg.addEventListener('mousemove', handleScanMove); + } + + function handleScanMove(e) { + if (e.buttons == 0) { + // Missed an end event, perhaps because mouse moved outside window. + setMode(IDLE); + svg.removeEventListener('mousemove', handleScanMove); + return; + } + if (mode == MOUSEPAN) panMove(e.clientX, e.clientY); + } + + function handleScanEnd(e) { + if (mode == MOUSEPAN) panMove(e.clientX, e.clientY); + setMode(IDLE); + svg.removeEventListener('mousemove', handleScanMove); + if (!moved) clickHandler(e.target); + } + + // Find touch object with specified identifier. + function findTouch(tlist, id) { + for (const t of tlist) { + if (t.identifier == id) return t; + } + return null; + } + + // Return distance between two touch points + function touchGap(t1, t2) { + const dx = t1.clientX - t2.clientX; + const dy = t1.clientY - t2.clientY; + return Math.hypot(dx, dy); + } + + function handleTouchStart(e) { + if (mode == IDLE && e.changedTouches.length == 1) { + // Start touch based panning + const t = e.changedTouches[0]; + setMode(TOUCHPAN); + touchid = t.identifier; + panStart(t.clientX, t.clientY); + e.preventDefault(); + } else if (mode == TOUCHPAN && e.touches.length == 2) { + // Start pinch zooming + setMode(TOUCHZOOM); + const t1 = e.touches[0]; + const t2 = e.touches[1]; + touchid = t1.identifier; + touchid2 = t2.identifier; + initScale = currentScale; + initGap = touchGap(t1, t2); + centerPoint = toSvg((t1.clientX + t2.clientX) / 2, + (t1.clientY + t2.clientY) / 2); + e.preventDefault(); + } + } + + function handleTouchMove(e) { + if (mode == TOUCHPAN) { + const t = findTouch(e.changedTouches, touchid); + if (t == null) return; + if (e.touches.length != 1) { + setMode(IDLE); + return; + } + panMove(t.clientX, t.clientY); + e.preventDefault(); + } else if (mode == TOUCHZOOM) { + // Get two touches; new gap; rescale to ratio. + const t1 = findTouch(e.touches, touchid); + const t2 = findTouch(e.touches, touchid2); + if (t1 == null || t2 == null) return; + const gap = touchGap(t1, t2); + rescale(initScale * gap / initGap, centerPoint); + e.preventDefault(); + } + } + + function handleTouchEnd(e) { + if (mode == TOUCHPAN) { + const t = findTouch(e.changedTouches, touchid); + if (t == null) return; + panMove(t.clientX, t.clientY); + setMode(IDLE); + e.preventDefault(); + if (!moved) clickHandler(t.target); + } else if (mode == TOUCHZOOM) { + setMode(IDLE); + e.preventDefault(); + } + } + + svg.addEventListener('mousedown', handleScanStart); + svg.addEventListener('mouseup', handleScanEnd); + svg.addEventListener('touchstart', handleTouchStart); + svg.addEventListener('touchmove', handleTouchMove); + svg.addEventListener('touchend', handleTouchEnd); + svg.addEventListener('wheel', handleWheel, true); +} + +function initMenus() { + 'use strict'; + + let activeMenu = null; + let activeMenuHdr = null; + + function cancelActiveMenu() { + if (activeMenu == null) return; + activeMenu.style.display = 'none'; + activeMenu = null; + activeMenuHdr = null; + } + + // Set click handlers on every menu header. + for (const menu of document.getElementsByClassName('submenu')) { + const hdr = menu.parentElement; + if (hdr == null) return; + if (hdr.classList.contains('disabled')) return; + function showMenu(e) { + // menu is a child of hdr, so this event can fire for clicks + // inside menu. Ignore such clicks. + if (e.target.parentElement != hdr) return; + activeMenu = menu; + activeMenuHdr = hdr; + menu.style.display = 'block'; + } + hdr.addEventListener('mousedown', showMenu); + hdr.addEventListener('touchstart', showMenu); + } + + // If there is an active menu and a down event outside, retract the menu. + for (const t of ['mousedown', 'touchstart']) { + document.addEventListener(t, (e) => { + // Note: to avoid unnecessary flicker, if the down event is inside + // the active menu header, do not retract the menu. + if (activeMenuHdr != e.target.closest('.menu-item')) { + cancelActiveMenu(); + } + }, { passive: true, capture: true }); + } + + // If there is an active menu and an up event inside, retract the menu. + document.addEventListener('mouseup', (e) => { + if (activeMenu == e.target.closest('.submenu')) { + cancelActiveMenu(); + } + }, { passive: true, capture: true }); +} + +function sendURL(method, url, done) { + fetch(url.toString(), {method: method}) + .then((response) => { done(response.ok); }) + .catch((error) => { done(false); }); +} + +// Initialize handlers for saving/loading configurations. +function initConfigManager() { + 'use strict'; + + // Initialize various elements. + function elem(id) { + const result = document.getElementById(id); + if (!result) console.warn('element ' + id + ' not found'); + return result; + } + const overlay = elem('dialog-overlay'); + const saveDialog = elem('save-dialog'); + const saveInput = elem('save-name'); + const saveError = elem('save-error'); + const delDialog = elem('delete-dialog'); + const delPrompt = elem('delete-prompt'); + const delError = elem('delete-error'); + + let currentDialog = null; + let currentDeleteTarget = null; + + function showDialog(dialog) { + if (currentDialog != null) { + overlay.style.display = 'none'; + currentDialog.style.display = 'none'; + } + currentDialog = dialog; + if (dialog != null) { + overlay.style.display = 'block'; + dialog.style.display = 'block'; + } + } + + function cancelDialog(e) { + showDialog(null); + } + + // Show dialog for saving the current config. + function showSaveDialog(e) { + saveError.innerText = ''; + showDialog(saveDialog); + saveInput.focus(); + } + + // Commit save config. + function commitSave(e) { + const name = saveInput.value; + const url = new URL(document.URL); + // Set path relative to existing path. + url.pathname = new URL('./saveconfig', document.URL).pathname; + url.searchParams.set('config', name); + saveError.innerText = ''; + sendURL('POST', url, (ok) => { + if (!ok) { + saveError.innerText = 'Save failed'; + } else { + showDialog(null); + location.reload(); // Reload to show updated config menu + } + }); + } + + function handleSaveInputKey(e) { + if (e.key === 'Enter') commitSave(e); + } + + function deleteConfig(e, elem) { + e.preventDefault(); + const config = elem.dataset.config; + delPrompt.innerText = 'Delete ' + config + '?'; + currentDeleteTarget = elem; + showDialog(delDialog); + } + + function commitDelete(e, elem) { + if (!currentDeleteTarget) return; + const config = currentDeleteTarget.dataset.config; + const url = new URL('./deleteconfig', document.URL); + url.searchParams.set('config', config); + delError.innerText = ''; + sendURL('DELETE', url, (ok) => { + if (!ok) { + delError.innerText = 'Delete failed'; + return; + } + showDialog(null); + // Remove menu entry for this config. + if (currentDeleteTarget && currentDeleteTarget.parentElement) { + currentDeleteTarget.parentElement.remove(); + } + }); + } + + // Bind event on elem to fn. + function bind(event, elem, fn) { + if (elem == null) return; + elem.addEventListener(event, fn); + if (event == 'click') { + // Also enable via touch. + elem.addEventListener('touchstart', fn); + } + } + + bind('click', elem('save-config'), showSaveDialog); + bind('click', elem('save-cancel'), cancelDialog); + bind('click', elem('save-confirm'), commitSave); + bind('keydown', saveInput, handleSaveInputKey); + + bind('click', elem('delete-cancel'), cancelDialog); + bind('click', elem('delete-confirm'), commitDelete); + + // Activate deletion button for all config entries in menu. + for (const del of Array.from(document.getElementsByClassName('menu-delete-btn'))) { + bind('click', del, (e) => { + deleteConfig(e, del); + }); + } +} + +function viewer(baseUrl, nodes) { + 'use strict'; + + // Elements + const search = document.getElementById('search'); + const graph0 = document.getElementById('graph0'); + const svg = (graph0 == null ? null : graph0.parentElement); + const toptable = document.getElementById('toptable'); + + let regexpActive = false; + let selected = new Map(); + let origFill = new Map(); + let searchAlarm = null; + let buttonsEnabled = true; + + function handleDetails(e) { + e.preventDefault(); + const detailsText = document.getElementById('detailsbox'); + if (detailsText != null) { + if (detailsText.style.display === 'block') { + detailsText.style.display = 'none'; + } else { + detailsText.style.display = 'block'; + } + } + } + + function handleKey(e) { + if (e.keyCode != 13) return; + setHrefParams(window.location, function (params) { + params.set('f', search.value); + }); + e.preventDefault(); + } + + function handleSearch() { + // Delay expensive processing so a flurry of key strokes is handled once. + if (searchAlarm != null) { + clearTimeout(searchAlarm); + } + searchAlarm = setTimeout(selectMatching, 300); + + regexpActive = true; + updateButtons(); + } + + function selectMatching() { + searchAlarm = null; + let re = null; + if (search.value != '') { + try { + re = new RegExp(search.value); + } catch (e) { + // TODO: Display error state in search box + return; + } + } + + function match(text) { + return re != null && re.test(text); + } + + // drop currently selected items that do not match re. + selected.forEach(function(v, n) { + if (!match(nodes[n])) { + unselect(n, document.getElementById('node' + n)); + } + }) + + // add matching items that are not currently selected. + if (nodes) { + for (let n = 0; n < nodes.length; n++) { + if (!selected.has(n) && match(nodes[n])) { + select(n, document.getElementById('node' + n)); + } + } + } + + updateButtons(); + } + + function toggleSvgSelect(elem) { + // Walk up to immediate child of graph0 + while (elem != null && elem.parentElement != graph0) { + elem = elem.parentElement; + } + if (!elem) return; + + // Disable regexp mode. + regexpActive = false; + + const n = nodeId(elem); + if (n < 0) return; + if (selected.has(n)) { + unselect(n, elem); + } else { + select(n, elem); + } + updateButtons(); + } + + function unselect(n, elem) { + if (elem == null) return; + selected.delete(n); + setBackground(elem, false); + } + + function select(n, elem) { + if (elem == null) return; + selected.set(n, true); + setBackground(elem, true); + } + + function nodeId(elem) { + const id = elem.id; + if (!id) return -1; + if (!id.startsWith('node')) return -1; + const n = parseInt(id.slice(4), 10); + if (isNaN(n)) return -1; + if (n < 0 || n >= nodes.length) return -1; + return n; + } + + function setBackground(elem, set) { + // Handle table row highlighting. + if (elem.nodeName == 'TR') { + elem.classList.toggle('hilite', set); + return; + } + + // Handle svg element highlighting. + const p = findPolygon(elem); + if (p != null) { + if (set) { + origFill.set(p, p.style.fill); + p.style.fill = '#ccccff'; + } else if (origFill.has(p)) { + p.style.fill = origFill.get(p); + } + } + } + + function findPolygon(elem) { + if (elem.localName == 'polygon') return elem; + for (const c of elem.children) { + const p = findPolygon(c); + if (p != null) return p; + } + return null; + } + + // convert a string to a regexp that matches that string. + function quotemeta(str) { + return str.replace(/([\\\.?+*\[\](){}|^$])/g, '\\$1'); + } + + function setSampleIndexLink(id) { + const elem = document.getElementById(id); + if (elem != null) { + setHrefParams(elem, function (params) { + params.set("si", id); + }); + } + } + + // Update id's href to reflect current selection whenever it is + // liable to be followed. + function makeSearchLinkDynamic(id) { + const elem = document.getElementById(id); + if (elem == null) return; + + // Most links copy current selection into the 'f' parameter, + // but Refine menu links are different. + let param = 'f'; + if (id == 'ignore') param = 'i'; + if (id == 'hide') param = 'h'; + if (id == 'show') param = 's'; + if (id == 'show-from') param = 'sf'; + + // We update on mouseenter so middle-click/right-click work properly. + elem.addEventListener('mouseenter', updater); + elem.addEventListener('touchstart', updater); + + function updater() { + // The selection can be in one of two modes: regexp-based or + // list-based. Construct regular expression depending on mode. + let re = regexpActive + ? search.value + : Array.from(selected.keys()).map(key => quotemeta(nodes[key])).join('|'); + + setHrefParams(elem, function (params) { + if (re != '') { + // For focus/show/show-from, forget old parameter. For others, add to re. + if (param != 'f' && param != 's' && param != 'sf' && params.has(param)) { + const old = params.get(param); + if (old != '') { + re += '|' + old; + } + } + params.set(param, re); + } else { + params.delete(param); + } + }); + } + } + + function setHrefParams(elem, paramSetter) { + let url = new URL(elem.href); + url.hash = ''; + + // Copy params from this page's URL. + const params = url.searchParams; + for (const p of new URLSearchParams(window.location.search)) { + params.set(p[0], p[1]); + } + + // Give the params to the setter to modify. + paramSetter(params); + + elem.href = url.toString(); + } + + function handleTopClick(e) { + // Walk back until we find TR and then get the Name column (index 5) + let elem = e.target; + while (elem != null && elem.nodeName != 'TR') { + elem = elem.parentElement; + } + if (elem == null || elem.children.length < 6) return; + + e.preventDefault(); + const tr = elem; + const td = elem.children[5]; + if (td.nodeName != 'TD') return; + const name = td.innerText; + const index = nodes.indexOf(name); + if (index < 0) return; + + // Disable regexp mode. + regexpActive = false; + + if (selected.has(index)) { + unselect(index, elem); + } else { + select(index, elem); + } + updateButtons(); + } + + function updateButtons() { + const enable = (search.value != '' || selected.size != 0); + if (buttonsEnabled == enable) return; + buttonsEnabled = enable; + for (const id of ['focus', 'ignore', 'hide', 'show', 'show-from']) { + const link = document.getElementById(id); + if (link != null) { + link.classList.toggle('disabled', !enable); + } + } + } + + // Initialize button states + updateButtons(); + + // Setup event handlers + initMenus(); + if (svg != null) { + initPanAndZoom(svg, toggleSvgSelect); + } + if (toptable != null) { + toptable.addEventListener('mousedown', handleTopClick); + toptable.addEventListener('touchstart', handleTopClick); + } + + const ids = ['topbtn', 'graphbtn', 'flamegraph', 'peek', 'list', 'disasm', + 'focus', 'ignore', 'hide', 'show', 'show-from']; + ids.forEach(makeSearchLinkDynamic); + + const sampleIDs = [{{range .SampleTypes}}'{{.}}', {{end}}]; + sampleIDs.forEach(setSampleIndexLink); + + // Bind action to button with specified id. + function addAction(id, action) { + const btn = document.getElementById(id); + if (btn != null) { + btn.addEventListener('click', action); + btn.addEventListener('touchstart', action); + } + } + + addAction('details', handleDetails); + initConfigManager(); + + search.addEventListener('input', handleSearch); + search.addEventListener('keydown', handleKey); + + // Give initial focus to main container so it can be scrolled using keys. + const main = document.getElementById('bodycontainer'); + if (main) { + main.focus(); + } +} diff --git a/src/cmd/vendor/github.com/google/pprof/internal/driver/html/flamegraph.html b/src/cmd/vendor/github.com/google/pprof/internal/driver/html/flamegraph.html new file mode 100644 index 0000000000..9866755bcd --- /dev/null +++ b/src/cmd/vendor/github.com/google/pprof/internal/driver/html/flamegraph.html @@ -0,0 +1,103 @@ +<!DOCTYPE html> +<html> +<head> + <meta charset="utf-8"> + <title>{{.Title}}</title> + {{template "css" .}} + <style type="text/css">{{template "d3flamegraphcss" .}}</style> + <style type="text/css"> + .flamegraph-content { + width: 90%; + min-width: 80%; + margin-left: 5%; + } + .flamegraph-details { + height: 1.2em; + width: 90%; + min-width: 90%; + margin-left: 5%; + padding: 15px 0 35px; + } + </style> +</head> +<body> + {{template "header" .}} + <div id="bodycontainer"> + <div id="flamegraphdetails" class="flamegraph-details"></div> + <div class="flamegraph-content"> + <div id="chart"></div> + </div> + </div> + {{template "script" .}} + <script>viewer(new URL(window.location.href), {{.Nodes}});</script> + <script>{{template "d3flamegraphscript" .}}</script> + <script> + {{- /* Deserialize as JSON instead of a JS object literal because the browser's + JSON parser can handle larger payloads than its JS parser. */ -}} + var data = JSON.parse("{{.FlameGraph}}"); + + var width = document.getElementById('chart').clientWidth; + + var flameGraph = flamegraph() + .width(width) + .cellHeight(18) + .minFrameSize(1) + .transitionDuration(750) + .inverted(true) + .sort(true) + .title('') + .tooltip(false) + .setDetailsElement(document.getElementById('flamegraphdetails')); + + // <full name> (percentage, value) + flameGraph.label((d) => d.data.f + ' (' + d.data.p + ', ' + d.data.l + ')'); + + flameGraph.setColorHue('warm'); + + select('#chart') + .datum(data) + .call(flameGraph); + + function clear() { + flameGraph.clear(); + } + + function resetZoom() { + flameGraph.resetZoom(); + } + + window.addEventListener('resize', function() { + var width = document.getElementById('chart').clientWidth; + var graphs = document.getElementsByClassName('d3-flame-graph'); + if (graphs.length > 0) { + graphs[0].setAttribute('width', width); + } + flameGraph.width(width); + flameGraph.resetZoom(); + }, true); + + var search = document.getElementById('search'); + var searchAlarm = null; + + function selectMatching() { + searchAlarm = null; + + if (search.value != '') { + flameGraph.search(search.value); + } else { + flameGraph.clear(); + } + } + + function handleSearch() { + // Delay expensive processing so a flurry of key strokes is handled once. + if (searchAlarm != null) { + clearTimeout(searchAlarm); + } + searchAlarm = setTimeout(selectMatching, 300); + } + + search.addEventListener('input', handleSearch); + </script> +</body> +</html> diff --git a/src/cmd/vendor/github.com/google/pprof/internal/driver/html/graph.html b/src/cmd/vendor/github.com/google/pprof/internal/driver/html/graph.html new file mode 100644 index 0000000000..a113549fc4 --- /dev/null +++ b/src/cmd/vendor/github.com/google/pprof/internal/driver/html/graph.html @@ -0,0 +1,16 @@ +<!DOCTYPE html> +<html> +<head> + <meta charset="utf-8"> + <title>{{.Title}}</title> + {{template "css" .}} +</head> +<body> + {{template "header" .}} + <div id="graph"> + {{.HTMLBody}} + </div> + {{template "script" .}} + <script>viewer(new URL(window.location.href), {{.Nodes}});</script> +</body> +</html> diff --git a/src/cmd/vendor/github.com/google/pprof/internal/driver/html/header.html b/src/cmd/vendor/github.com/google/pprof/internal/driver/html/header.html new file mode 100644 index 0000000000..66cabbbaa4 --- /dev/null +++ b/src/cmd/vendor/github.com/google/pprof/internal/driver/html/header.html @@ -0,0 +1,113 @@ +<div class="header"> + <div class="title"> + <h1><a href="./">pprof</a></h1> + </div> + + <div id="view" class="menu-item"> + <div class="menu-name"> + View + <i class="downArrow"></i> + </div> + <div class="submenu"> + <a title="{{.Help.top}}" href="./top" id="topbtn">Top</a> + <a title="{{.Help.graph}}" href="./" id="graphbtn">Graph</a> + <a title="{{.Help.flamegraph}}" href="./flamegraph" id="flamegraph">Flame Graph</a> + <a title="{{.Help.peek}}" href="./peek" id="peek">Peek</a> + <a title="{{.Help.list}}" href="./source" id="list">Source</a> + <a title="{{.Help.disasm}}" href="./disasm" id="disasm">Disassemble</a> + </div> + </div> + + {{$sampleLen := len .SampleTypes}} + {{if gt $sampleLen 1}} + <div id="sample" class="menu-item"> + <div class="menu-name"> + Sample + <i class="downArrow"></i> + </div> + <div class="submenu"> + {{range .SampleTypes}} + <a href="?si={{.}}" id="{{.}}">{{.}}</a> + {{end}} + </div> + </div> + {{end}} + + <div id="refine" class="menu-item"> + <div class="menu-name"> + Refine + <i class="downArrow"></i> + </div> + <div class="submenu"> + <a title="{{.Help.focus}}" href="?" id="focus">Focus</a> + <a title="{{.Help.ignore}}" href="?" id="ignore">Ignore</a> + <a title="{{.Help.hide}}" href="?" id="hide">Hide</a> + <a title="{{.Help.show}}" href="?" id="show">Show</a> + <a title="{{.Help.show_from}}" href="?" id="show-from">Show from</a> + <hr> + <a title="{{.Help.reset}}" href="?">Reset</a> + </div> + </div> + + <div id="config" class="menu-item"> + <div class="menu-name"> + Config + <i class="downArrow"></i> + </div> + <div class="submenu"> + <a title="{{.Help.save_config}}" id="save-config">Save as ...</a> + <hr> + {{range .Configs}} + <a href="{{.URL}}"> + {{if .Current}}<span class="menu-check-mark">✓</span>{{end}} + {{.Name}} + {{if .UserConfig}}<span class="menu-delete-btn" data-config={{.Name}}>🗙</span>{{end}} + </a> + {{end}} + </div> + </div> + + <div id="download" class="menu-item"> + <div class="menu-name"> + <a href="./download">Download</a> + </div> + </div> + + <div> + <input id="search" type="text" placeholder="Search regexp" autocomplete="off" autocapitalize="none" size=40> + </div> + + <div class="description"> + <a title="{{.Help.details}}" href="#" id="details">{{.Title}}</a> + <div id="detailsbox"> + {{range .Legend}}<div>{{.}}</div>{{end}} + </div> + </div> +</div> + +<div id="dialog-overlay"></div> + +<div class="dialog" id="save-dialog"> + <div class="dialog-header">Save options as</div> + <datalist id="config-list"> + {{range .Configs}}{{if .UserConfig}}<option value="{{.Name}}" />{{end}}{{end}} + </datalist> + <input id="save-name" type="text" list="config-list" placeholder="New config" /> + <div class="dialog-footer"> + <span class="dialog-error" id="save-error"></span> + <button id="save-cancel">Cancel</button> + <button id="save-confirm">Save</button> + </div> +</div> + +<div class="dialog" id="delete-dialog"> + <div class="dialog-header" id="delete-dialog-title">Delete config</div> + <div id="delete-prompt"></div> + <div class="dialog-footer"> + <span class="dialog-error" id="delete-error"></span> + <button id="delete-cancel">Cancel</button> + <button id="delete-confirm">Delete</button> + </div> +</div> + +<div id="errors">{{range .Errors}}<div>{{.}}</div>{{end}}</div> diff --git a/src/cmd/vendor/github.com/google/pprof/internal/driver/html/plaintext.html b/src/cmd/vendor/github.com/google/pprof/internal/driver/html/plaintext.html new file mode 100644 index 0000000000..9791cc7718 --- /dev/null +++ b/src/cmd/vendor/github.com/google/pprof/internal/driver/html/plaintext.html @@ -0,0 +1,18 @@ +<!DOCTYPE html> +<html> +<head> + <meta charset="utf-8"> + <title>{{.Title}}</title> + {{template "css" .}} +</head> +<body> + {{template "header" .}} + <div id="content"> + <pre> + {{.TextBody}} + </pre> + </div> + {{template "script" .}} + <script>viewer(new URL(window.location.href), null);</script> +</body> +</html> diff --git a/src/cmd/vendor/github.com/google/pprof/internal/driver/html/source.html b/src/cmd/vendor/github.com/google/pprof/internal/driver/html/source.html new file mode 100644 index 0000000000..3212bee4a0 --- /dev/null +++ b/src/cmd/vendor/github.com/google/pprof/internal/driver/html/source.html @@ -0,0 +1,18 @@ +<!DOCTYPE html> +<html> +<head> + <meta charset="utf-8"> + <title>{{.Title}}</title> + {{template "css" .}} + {{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> +</html> diff --git a/src/cmd/vendor/github.com/google/pprof/internal/driver/html/top.html b/src/cmd/vendor/github.com/google/pprof/internal/driver/html/top.html new file mode 100644 index 0000000000..86d9fcbdb0 --- /dev/null +++ b/src/cmd/vendor/github.com/google/pprof/internal/driver/html/top.html @@ -0,0 +1,114 @@ +<!DOCTYPE html> +<html> +<head> + <meta charset="utf-8"> + <title>{{.Title}}</title> + {{template "css" .}} + <style type="text/css"> + </style> +</head> +<body> + {{template "header" .}} + <div id="top"> + <table id="toptable"> + <thead> + <tr> + <th id="flathdr1">Flat</th> + <th id="flathdr2">Flat%</th> + <th>Sum%</th> + <th id="cumhdr1">Cum</th> + <th id="cumhdr2">Cum%</th> + <th id="namehdr">Name</th> + <th>Inlined?</th> + </tr> + </thead> + <tbody id="rows"></tbody> + </table> + </div> + {{template "script" .}} + <script> + function makeTopTable(total, entries) { + const rows = document.getElementById('rows'); + if (rows == null) return; + + // Store initial index in each entry so we have stable node ids for selection. + for (let i = 0; i < entries.length; i++) { + entries[i].Id = 'node' + i; + } + + // Which column are we currently sorted by and in what order? + let currentColumn = ''; + let descending = false; + sortBy('Flat'); + + function sortBy(column) { + // Update sort criteria + if (column == currentColumn) { + descending = !descending; // Reverse order + } else { + currentColumn = column; + descending = (column != 'Name'); + } + + // Sort according to current criteria. + function cmp(a, b) { + const av = a[currentColumn]; + const bv = b[currentColumn]; + if (av < bv) return -1; + if (av > bv) return +1; + return 0; + } + entries.sort(cmp); + if (descending) entries.reverse(); + + function addCell(tr, val) { + const td = document.createElement('td'); + td.textContent = val; + tr.appendChild(td); + } + + function percent(v) { + return (v * 100.0 / total).toFixed(2) + '%'; + } + + // Generate rows + const fragment = document.createDocumentFragment(); + let sum = 0; + for (const row of entries) { + const tr = document.createElement('tr'); + tr.id = row.Id; + sum += row.Flat; + addCell(tr, row.FlatFormat); + addCell(tr, percent(row.Flat)); + addCell(tr, percent(sum)); + addCell(tr, row.CumFormat); + addCell(tr, percent(row.Cum)); + addCell(tr, row.Name); + addCell(tr, row.InlineLabel); + fragment.appendChild(tr); + } + + rows.textContent = ''; // Remove old rows + rows.appendChild(fragment); + } + + // Make different column headers trigger sorting. + function bindSort(id, column) { + const hdr = document.getElementById(id); + if (hdr == null) return; + const fn = function() { sortBy(column) }; + hdr.addEventListener('click', fn); + hdr.addEventListener('touch', fn); + } + bindSort('flathdr1', 'Flat'); + bindSort('flathdr2', 'Flat'); + bindSort('cumhdr1', 'Cum'); + bindSort('cumhdr2', 'Cum'); + bindSort('namehdr', 'Name'); + } + + viewer(new URL(window.location.href), {{.Nodes}}); + makeTopTable({{.Total}}, {{.Top}}); + </script> +</body> +</html> diff --git a/src/cmd/vendor/github.com/google/pprof/internal/driver/tagroot.go b/src/cmd/vendor/github.com/google/pprof/internal/driver/tagroot.go index c2cdfa455e..c43d599982 100644 --- a/src/cmd/vendor/github.com/google/pprof/internal/driver/tagroot.go +++ b/src/cmd/vendor/github.com/google/pprof/internal/driver/tagroot.go @@ -113,12 +113,17 @@ func formatLabelValues(s *profile.Sample, k string, outputUnit string) []string values = append(values, s.Label[k]...) numLabels := s.NumLabel[k] numUnits := s.NumUnit[k] - if len(numLabels) != len(numUnits) { + if len(numLabels) != len(numUnits) && len(numUnits) != 0 { return values } for i, numLabel := range numLabels { - unit := numUnits[i] - values = append(values, measurement.ScaledLabel(numLabel, unit, outputUnit)) + var value string + if len(numUnits) != 0 { + value = measurement.ScaledLabel(numLabel, numUnits[i], outputUnit) + } else { + value = measurement.ScaledLabel(numLabel, "", "") + } + values = append(values, value) } return values } diff --git a/src/cmd/vendor/github.com/google/pprof/internal/driver/webhtml.go b/src/cmd/vendor/github.com/google/pprof/internal/driver/webhtml.go index 63df668321..94f32e3755 100644 --- a/src/cmd/vendor/github.com/google/pprof/internal/driver/webhtml.go +++ b/src/cmd/vendor/github.com/google/pprof/internal/driver/webhtml.go @@ -15,1387 +15,54 @@ package driver import ( + "embed" + "fmt" "html/template" + "os" "github.com/google/pprof/third_party/d3flamegraph" ) +//go:embed html +var embeddedFiles embed.FS + // addTemplates adds a set of template definitions to templates. func addTemplates(templates *template.Template) { - template.Must(templates.Parse(`{{define "d3flamegraphscript"}}` + d3flamegraph.JSSource + `{{end}}`)) - template.Must(templates.Parse(`{{define "d3flamegraphcss"}}` + d3flamegraph.CSSSource + `{{end}}`)) - template.Must(templates.Parse(` -{{define "css"}} -<style type="text/css"> -* { - margin: 0; - padding: 0; - box-sizing: border-box; -} -html, body { - height: 100%; -} -body { - font-family: 'Roboto', -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; - font-size: 13px; - line-height: 1.4; - display: flex; - flex-direction: column; -} -a { - color: #2a66d9; -} -.header { - display: flex; - align-items: center; - height: 44px; - min-height: 44px; - background-color: #eee; - color: #212121; - padding: 0 1rem; -} -.header > div { - margin: 0 0.125em; -} -.header .title h1 { - font-size: 1.75em; - margin-right: 1rem; - margin-bottom: 4px; -} -.header .title a { - color: #212121; - text-decoration: none; -} -.header .title a:hover { - text-decoration: underline; -} -.header .description { - width: 100%; - text-align: right; - white-space: nowrap; -} -@media screen and (max-width: 799px) { - .header input { - display: none; - } -} -#detailsbox { - display: none; - z-index: 1; - position: fixed; - top: 40px; - right: 20px; - background-color: #ffffff; - box-shadow: 0 1px 5px rgba(0,0,0,.3); - line-height: 24px; - padding: 1em; - text-align: left; -} -.header input { - background: white url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' style='pointer-events:none;display:block;width:100%25;height:100%25;fill:%23757575'%3E%3Cpath d='M15.5 14h-.79l-.28-.27C15.41 12.59 16 11.11 16 9.5 16 5.91 13.09 3 9.5 3S3 5.91 3 9.5 5.91 16 9.5 16c1.61.0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z'/%3E%3C/svg%3E") no-repeat 4px center/20px 20px; - border: 1px solid #d1d2d3; - border-radius: 2px 0 0 2px; - padding: 0.25em; - padding-left: 28px; - margin-left: 1em; - font-family: 'Roboto', 'Noto', sans-serif; - font-size: 1em; - line-height: 24px; - color: #212121; -} -.downArrow { - border-top: .36em solid #ccc; - border-left: .36em solid transparent; - border-right: .36em solid transparent; - margin-bottom: .05em; - margin-left: .5em; - transition: border-top-color 200ms; -} -.menu-item { - height: 100%; - text-transform: uppercase; - font-family: 'Roboto Medium', -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; - position: relative; -} -.menu-item .menu-name:hover { - opacity: 0.75; -} -.menu-item .menu-name:hover .downArrow { - border-top-color: #666; -} -.menu-name { - height: 100%; - padding: 0 0.5em; - display: flex; - align-items: center; - justify-content: center; -} -.menu-name a { - text-decoration: none; - color: #212121; -} -.submenu { - display: none; - z-index: 1; - margin-top: -4px; - min-width: 10em; - position: absolute; - left: 0px; - background-color: white; - box-shadow: 0 1px 5px rgba(0,0,0,.3); - font-size: 100%; - text-transform: none; -} -.menu-item, .submenu { - user-select: none; - -moz-user-select: none; - -ms-user-select: none; - -webkit-user-select: none; -} -.submenu hr { - border: 0; - border-top: 2px solid #eee; -} -.submenu a { - display: block; - padding: .5em 1em; - text-decoration: none; -} -.submenu a:hover, .submenu a.active { - color: white; - background-color: #6b82d6; -} -.submenu a.disabled { - color: gray; - pointer-events: none; -} -.menu-check-mark { - position: absolute; - left: 2px; -} -.menu-delete-btn { - position: absolute; - right: 2px; -} - -{{/* Used to disable events when a modal dialog is displayed */}} -#dialog-overlay { - display: none; - position: fixed; - left: 0px; - top: 0px; - width: 100%; - height: 100%; - background-color: rgba(1,1,1,0.1); -} - -.dialog { - {{/* Displayed centered horizontally near the top */}} - display: none; - position: fixed; - margin: 0px; - top: 60px; - left: 50%; - transform: translateX(-50%); - - z-index: 3; - font-size: 125%; - background-color: #ffffff; - box-shadow: 0 1px 5px rgba(0,0,0,.3); -} -.dialog-header { - font-size: 120%; - border-bottom: 1px solid #CCCCCC; - width: 100%; - text-align: center; - background: #EEEEEE; - user-select: none; -} -.dialog-footer { - border-top: 1px solid #CCCCCC; - width: 100%; - text-align: right; - padding: 10px; -} -.dialog-error { - margin: 10px; - color: red; -} -.dialog input { - margin: 10px; - font-size: inherit; -} -.dialog button { - margin-left: 10px; - font-size: inherit; -} -#save-dialog, #delete-dialog { - width: 50%; - max-width: 20em; -} -#delete-prompt { - padding: 10px; -} - -#content { - overflow-y: scroll; - padding: 1em; -} -#top { - overflow-y: scroll; -} -#graph { - overflow: hidden; -} -#graph svg { - width: 100%; - height: auto; - padding: 10px; -} -#content.source .filename { - margin-top: 0; - margin-bottom: 1em; - font-size: 120%; -} -#content.source pre { - margin-bottom: 3em; -} -table { - border-spacing: 0px; - width: 100%; - padding-bottom: 1em; - white-space: nowrap; -} -table thead { - font-family: 'Roboto Medium', -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; -} -table tr th { - position: sticky; - top: 0; - background-color: #ddd; - text-align: right; - padding: .3em .5em; -} -table tr td { - padding: .3em .5em; - text-align: right; -} -#top table tr th:nth-child(6), -#top table tr th:nth-child(7), -#top table tr td:nth-child(6), -#top table tr td:nth-child(7) { - text-align: left; -} -#top table tr td:nth-child(6) { - width: 100%; - text-overflow: ellipsis; - overflow: hidden; - white-space: nowrap; -} -#flathdr1, #flathdr2, #cumhdr1, #cumhdr2, #namehdr { - cursor: ns-resize; -} -.hilite { - background-color: #ebf5fb; - font-weight: bold; -} -</style> -{{end}} - -{{define "header"}} -<div class="header"> - <div class="title"> - <h1><a href="./">pprof</a></h1> - </div> - - <div id="view" class="menu-item"> - <div class="menu-name"> - View - <i class="downArrow"></i> - </div> - <div class="submenu"> - <a title="{{.Help.top}}" href="./top" id="topbtn">Top</a> - <a title="{{.Help.graph}}" href="./" id="graphbtn">Graph</a> - <a title="{{.Help.flamegraph}}" href="./flamegraph" id="flamegraph">Flame Graph</a> - <a title="{{.Help.peek}}" href="./peek" id="peek">Peek</a> - <a title="{{.Help.list}}" href="./source" id="list">Source</a> - <a title="{{.Help.disasm}}" href="./disasm" id="disasm">Disassemble</a> - </div> - </div> - - {{$sampleLen := len .SampleTypes}} - {{if gt $sampleLen 1}} - <div id="sample" class="menu-item"> - <div class="menu-name"> - Sample - <i class="downArrow"></i> - </div> - <div class="submenu"> - {{range .SampleTypes}} - <a href="?si={{.}}" id="{{.}}">{{.}}</a> - {{end}} - </div> - </div> - {{end}} - - <div id="refine" class="menu-item"> - <div class="menu-name"> - Refine - <i class="downArrow"></i> - </div> - <div class="submenu"> - <a title="{{.Help.focus}}" href="?" id="focus">Focus</a> - <a title="{{.Help.ignore}}" href="?" id="ignore">Ignore</a> - <a title="{{.Help.hide}}" href="?" id="hide">Hide</a> - <a title="{{.Help.show}}" href="?" id="show">Show</a> - <a title="{{.Help.show_from}}" href="?" id="show-from">Show from</a> - <hr> - <a title="{{.Help.reset}}" href="?">Reset</a> - </div> - </div> - - <div id="config" class="menu-item"> - <div class="menu-name"> - Config - <i class="downArrow"></i> - </div> - <div class="submenu"> - <a title="{{.Help.save_config}}" id="save-config">Save as ...</a> - <hr> - {{range .Configs}} - <a href="{{.URL}}"> - {{if .Current}}<span class="menu-check-mark">✓</span>{{end}} - {{.Name}} - {{if .UserConfig}}<span class="menu-delete-btn" data-config={{.Name}}>🗙</span>{{end}} - </a> - {{end}} - </div> - </div> - - <div id="download" class="menu-item"> - <div class="menu-name"> - <a href="./download">Download</a> - </div> - </div> - - <div> - <input id="search" type="text" placeholder="Search regexp" autocomplete="off" autocapitalize="none" size=40> - </div> - - <div class="description"> - <a title="{{.Help.details}}" href="#" id="details">{{.Title}}</a> - <div id="detailsbox"> - {{range .Legend}}<div>{{.}}</div>{{end}} - </div> - </div> -</div> - -<div id="dialog-overlay"></div> - -<div class="dialog" id="save-dialog"> - <div class="dialog-header">Save options as</div> - <datalist id="config-list"> - {{range .Configs}}{{if .UserConfig}}<option value="{{.Name}}" />{{end}}{{end}} - </datalist> - <input id="save-name" type="text" list="config-list" placeholder="New config" /> - <div class="dialog-footer"> - <span class="dialog-error" id="save-error"></span> - <button id="save-cancel">Cancel</button> - <button id="save-confirm">Save</button> - </div> -</div> - -<div class="dialog" id="delete-dialog"> - <div class="dialog-header" id="delete-dialog-title">Delete config</div> - <div id="delete-prompt"></div> - <div class="dialog-footer"> - <span class="dialog-error" id="delete-error"></span> - <button id="delete-cancel">Cancel</button> - <button id="delete-confirm">Delete</button> - </div> -</div> - -<div id="errors">{{range .Errors}}<div>{{.}}</div>{{end}}</div> -{{end}} - -{{define "graph" -}} -<!DOCTYPE html> -<html> -<head> - <meta charset="utf-8"> - <title>{{.Title}}</title> - {{template "css" .}} -</head> -<body> - {{template "header" .}} - <div id="graph"> - {{.HTMLBody}} - </div> - {{template "script" .}} - <script>viewer(new URL(window.location.href), {{.Nodes}});</script> -</body> -</html> -{{end}} - -{{define "script"}} -<script> -// Make svg pannable and zoomable. -// Call clickHandler(t) if a click event is caught by the pan event handlers. -function initPanAndZoom(svg, clickHandler) { - 'use strict'; - - // Current mouse/touch handling mode - const IDLE = 0; - const MOUSEPAN = 1; - const TOUCHPAN = 2; - const TOUCHZOOM = 3; - let mode = IDLE; - - // State needed to implement zooming. - let currentScale = 1.0; - const initWidth = svg.viewBox.baseVal.width; - const initHeight = svg.viewBox.baseVal.height; - - // State needed to implement panning. - let panLastX = 0; // Last event X coordinate - let panLastY = 0; // Last event Y coordinate - let moved = false; // Have we seen significant movement - let touchid = null; // Current touch identifier - - // State needed for pinch zooming - let touchid2 = null; // Second id for pinch zooming - let initGap = 1.0; // Starting gap between two touches - let initScale = 1.0; // currentScale when pinch zoom started - let centerPoint = null; // Center point for scaling - - // Convert event coordinates to svg coordinates. - function toSvg(x, y) { - const p = svg.createSVGPoint(); - p.x = x; - p.y = y; - let m = svg.getCTM(); - if (m == null) m = svg.getScreenCTM(); // Firefox workaround. - return p.matrixTransform(m.inverse()); - } - - // Change the scaling for the svg to s, keeping the point denoted - // by u (in svg coordinates]) fixed at the same screen location. - function rescale(s, u) { - // Limit to a good range. - if (s < 0.2) s = 0.2; - if (s > 10.0) s = 10.0; - - currentScale = s; - - // svg.viewBox defines the visible portion of the user coordinate - // system. So to magnify by s, divide the visible portion by s, - // which will then be stretched to fit the viewport. - const vb = svg.viewBox; - const w1 = vb.baseVal.width; - const w2 = initWidth / s; - const h1 = vb.baseVal.height; - const h2 = initHeight / s; - vb.baseVal.width = w2; - vb.baseVal.height = h2; - - // We also want to adjust vb.baseVal.x so that u.x remains at same - // screen X coordinate. In other words, want to change it from x1 to x2 - // so that: - // (u.x - x1) / w1 = (u.x - x2) / w2 - // Simplifying that, we get - // (u.x - x1) * (w2 / w1) = u.x - x2 - // x2 = u.x - (u.x - x1) * (w2 / w1) - vb.baseVal.x = u.x - (u.x - vb.baseVal.x) * (w2 / w1); - vb.baseVal.y = u.y - (u.y - vb.baseVal.y) * (h2 / h1); - } - - function handleWheel(e) { - if (e.deltaY == 0) return; - // Change scale factor by 1.1 or 1/1.1 - rescale(currentScale * (e.deltaY < 0 ? 1.1 : (1/1.1)), - toSvg(e.offsetX, e.offsetY)); - } - - function setMode(m) { - mode = m; - touchid = null; - touchid2 = null; - } - - function panStart(x, y) { - moved = false; - panLastX = x; - panLastY = y; - } - - function panMove(x, y) { - let dx = x - panLastX; - let dy = y - panLastY; - if (Math.abs(dx) <= 2 && Math.abs(dy) <= 2) return; // Ignore tiny moves - - moved = true; - panLastX = x; - panLastY = y; - - // Firefox workaround: get dimensions from parentNode. - const swidth = svg.clientWidth || svg.parentNode.clientWidth; - const sheight = svg.clientHeight || svg.parentNode.clientHeight; - - // Convert deltas from screen space to svg space. - dx *= (svg.viewBox.baseVal.width / swidth); - dy *= (svg.viewBox.baseVal.height / sheight); - - svg.viewBox.baseVal.x -= dx; - svg.viewBox.baseVal.y -= dy; - } - - function handleScanStart(e) { - if (e.button != 0) return; // Do not catch right-clicks etc. - setMode(MOUSEPAN); - panStart(e.clientX, e.clientY); - e.preventDefault(); - svg.addEventListener('mousemove', handleScanMove); - } - - function handleScanMove(e) { - if (e.buttons == 0) { - // Missed an end event, perhaps because mouse moved outside window. - setMode(IDLE); - svg.removeEventListener('mousemove', handleScanMove); - return; - } - if (mode == MOUSEPAN) panMove(e.clientX, e.clientY); - } - - function handleScanEnd(e) { - if (mode == MOUSEPAN) panMove(e.clientX, e.clientY); - setMode(IDLE); - svg.removeEventListener('mousemove', handleScanMove); - if (!moved) clickHandler(e.target); - } - - // Find touch object with specified identifier. - function findTouch(tlist, id) { - for (const t of tlist) { - if (t.identifier == id) return t; - } - return null; - } - - // Return distance between two touch points - function touchGap(t1, t2) { - const dx = t1.clientX - t2.clientX; - const dy = t1.clientY - t2.clientY; - return Math.hypot(dx, dy); - } - - function handleTouchStart(e) { - if (mode == IDLE && e.changedTouches.length == 1) { - // Start touch based panning - const t = e.changedTouches[0]; - setMode(TOUCHPAN); - touchid = t.identifier; - panStart(t.clientX, t.clientY); - e.preventDefault(); - } else if (mode == TOUCHPAN && e.touches.length == 2) { - // Start pinch zooming - setMode(TOUCHZOOM); - const t1 = e.touches[0]; - const t2 = e.touches[1]; - touchid = t1.identifier; - touchid2 = t2.identifier; - initScale = currentScale; - initGap = touchGap(t1, t2); - centerPoint = toSvg((t1.clientX + t2.clientX) / 2, - (t1.clientY + t2.clientY) / 2); - e.preventDefault(); - } - } - - function handleTouchMove(e) { - if (mode == TOUCHPAN) { - const t = findTouch(e.changedTouches, touchid); - if (t == null) return; - if (e.touches.length != 1) { - setMode(IDLE); - return; - } - panMove(t.clientX, t.clientY); - e.preventDefault(); - } else if (mode == TOUCHZOOM) { - // Get two touches; new gap; rescale to ratio. - const t1 = findTouch(e.touches, touchid); - const t2 = findTouch(e.touches, touchid2); - if (t1 == null || t2 == null) return; - const gap = touchGap(t1, t2); - rescale(initScale * gap / initGap, centerPoint); - e.preventDefault(); - } - } - - function handleTouchEnd(e) { - if (mode == TOUCHPAN) { - const t = findTouch(e.changedTouches, touchid); - if (t == null) return; - panMove(t.clientX, t.clientY); - setMode(IDLE); - e.preventDefault(); - if (!moved) clickHandler(t.target); - } else if (mode == TOUCHZOOM) { - setMode(IDLE); - e.preventDefault(); - } - } - - svg.addEventListener('mousedown', handleScanStart); - svg.addEventListener('mouseup', handleScanEnd); - svg.addEventListener('touchstart', handleTouchStart); - svg.addEventListener('touchmove', handleTouchMove); - svg.addEventListener('touchend', handleTouchEnd); - svg.addEventListener('wheel', handleWheel, true); -} - -function initMenus() { - 'use strict'; - - let activeMenu = null; - let activeMenuHdr = null; - - function cancelActiveMenu() { - if (activeMenu == null) return; - activeMenu.style.display = 'none'; - activeMenu = null; - activeMenuHdr = null; - } - - // Set click handlers on every menu header. - for (const menu of document.getElementsByClassName('submenu')) { - const hdr = menu.parentElement; - if (hdr == null) return; - if (hdr.classList.contains('disabled')) return; - function showMenu(e) { - // menu is a child of hdr, so this event can fire for clicks - // inside menu. Ignore such clicks. - if (e.target.parentElement != hdr) return; - activeMenu = menu; - activeMenuHdr = hdr; - menu.style.display = 'block'; - } - hdr.addEventListener('mousedown', showMenu); - hdr.addEventListener('touchstart', showMenu); - } - - // If there is an active menu and a down event outside, retract the menu. - for (const t of ['mousedown', 'touchstart']) { - document.addEventListener(t, (e) => { - // Note: to avoid unnecessary flicker, if the down event is inside - // the active menu header, do not retract the menu. - if (activeMenuHdr != e.target.closest('.menu-item')) { - cancelActiveMenu(); - } - }, { passive: true, capture: true }); - } - - // If there is an active menu and an up event inside, retract the menu. - document.addEventListener('mouseup', (e) => { - if (activeMenu == e.target.closest('.submenu')) { - cancelActiveMenu(); - } - }, { passive: true, capture: true }); -} - -function sendURL(method, url, done) { - fetch(url.toString(), {method: method}) - .then((response) => { done(response.ok); }) - .catch((error) => { done(false); }); -} - -// Initialize handlers for saving/loading configurations. -function initConfigManager() { - 'use strict'; - - // Initialize various elements. - function elem(id) { - const result = document.getElementById(id); - if (!result) console.warn('element ' + id + ' not found'); - return result; - } - const overlay = elem('dialog-overlay'); - const saveDialog = elem('save-dialog'); - const saveInput = elem('save-name'); - const saveError = elem('save-error'); - const delDialog = elem('delete-dialog'); - const delPrompt = elem('delete-prompt'); - const delError = elem('delete-error'); - - let currentDialog = null; - let currentDeleteTarget = null; - - function showDialog(dialog) { - if (currentDialog != null) { - overlay.style.display = 'none'; - currentDialog.style.display = 'none'; - } - currentDialog = dialog; - if (dialog != null) { - overlay.style.display = 'block'; - dialog.style.display = 'block'; - } - } - - function cancelDialog(e) { - showDialog(null); - } - - // Show dialog for saving the current config. - function showSaveDialog(e) { - saveError.innerText = ''; - showDialog(saveDialog); - saveInput.focus(); - } - - // Commit save config. - function commitSave(e) { - const name = saveInput.value; - const url = new URL(document.URL); - // Set path relative to existing path. - url.pathname = new URL('./saveconfig', document.URL).pathname; - url.searchParams.set('config', name); - saveError.innerText = ''; - sendURL('POST', url, (ok) => { - if (!ok) { - saveError.innerText = 'Save failed'; - } else { - showDialog(null); - location.reload(); // Reload to show updated config menu - } - }); - } - - function handleSaveInputKey(e) { - if (e.key === 'Enter') commitSave(e); - } - - function deleteConfig(e, elem) { - e.preventDefault(); - const config = elem.dataset.config; - delPrompt.innerText = 'Delete ' + config + '?'; - currentDeleteTarget = elem; - showDialog(delDialog); - } - - function commitDelete(e, elem) { - if (!currentDeleteTarget) return; - const config = currentDeleteTarget.dataset.config; - const url = new URL('./deleteconfig', document.URL); - url.searchParams.set('config', config); - delError.innerText = ''; - sendURL('DELETE', url, (ok) => { - if (!ok) { - delError.innerText = 'Delete failed'; - return; - } - showDialog(null); - // Remove menu entry for this config. - if (currentDeleteTarget && currentDeleteTarget.parentElement) { - currentDeleteTarget.parentElement.remove(); - } - }); - } - - // Bind event on elem to fn. - function bind(event, elem, fn) { - if (elem == null) return; - elem.addEventListener(event, fn); - if (event == 'click') { - // Also enable via touch. - elem.addEventListener('touchstart', fn); - } - } - - bind('click', elem('save-config'), showSaveDialog); - bind('click', elem('save-cancel'), cancelDialog); - bind('click', elem('save-confirm'), commitSave); - bind('keydown', saveInput, handleSaveInputKey); - - bind('click', elem('delete-cancel'), cancelDialog); - bind('click', elem('delete-confirm'), commitDelete); - - // Activate deletion button for all config entries in menu. - for (const del of Array.from(document.getElementsByClassName('menu-delete-btn'))) { - bind('click', del, (e) => { - deleteConfig(e, del); - }); - } -} - -function viewer(baseUrl, nodes) { - 'use strict'; - - // Elements - const search = document.getElementById('search'); - const graph0 = document.getElementById('graph0'); - const svg = (graph0 == null ? null : graph0.parentElement); - const toptable = document.getElementById('toptable'); - - let regexpActive = false; - let selected = new Map(); - let origFill = new Map(); - let searchAlarm = null; - let buttonsEnabled = true; - - function handleDetails(e) { - e.preventDefault(); - const detailsText = document.getElementById('detailsbox'); - if (detailsText != null) { - if (detailsText.style.display === 'block') { - detailsText.style.display = 'none'; - } else { - detailsText.style.display = 'block'; - } - } - } - - function handleKey(e) { - if (e.keyCode != 13) return; - setHrefParams(window.location, function (params) { - params.set('f', search.value); - }); - e.preventDefault(); - } - - function handleSearch() { - // Delay expensive processing so a flurry of key strokes is handled once. - if (searchAlarm != null) { - clearTimeout(searchAlarm); - } - searchAlarm = setTimeout(selectMatching, 300); - - regexpActive = true; - updateButtons(); - } - - function selectMatching() { - searchAlarm = null; - let re = null; - if (search.value != '') { - try { - re = new RegExp(search.value); - } catch (e) { - // TODO: Display error state in search box - return; - } - } - - function match(text) { - return re != null && re.test(text); - } - - // drop currently selected items that do not match re. - selected.forEach(function(v, n) { - if (!match(nodes[n])) { - unselect(n, document.getElementById('node' + n)); - } - }) - - // add matching items that are not currently selected. - if (nodes) { - for (let n = 0; n < nodes.length; n++) { - if (!selected.has(n) && match(nodes[n])) { - select(n, document.getElementById('node' + n)); - } - } - } - - updateButtons(); - } - - function toggleSvgSelect(elem) { - // Walk up to immediate child of graph0 - while (elem != null && elem.parentElement != graph0) { - elem = elem.parentElement; - } - if (!elem) return; - - // Disable regexp mode. - regexpActive = false; - - const n = nodeId(elem); - if (n < 0) return; - if (selected.has(n)) { - unselect(n, elem); - } else { - select(n, elem); - } - updateButtons(); - } - - function unselect(n, elem) { - if (elem == null) return; - selected.delete(n); - setBackground(elem, false); - } - - function select(n, elem) { - if (elem == null) return; - selected.set(n, true); - setBackground(elem, true); - } - - function nodeId(elem) { - const id = elem.id; - if (!id) return -1; - if (!id.startsWith('node')) return -1; - const n = parseInt(id.slice(4), 10); - if (isNaN(n)) return -1; - if (n < 0 || n >= nodes.length) return -1; - return n; - } - - function setBackground(elem, set) { - // Handle table row highlighting. - if (elem.nodeName == 'TR') { - elem.classList.toggle('hilite', set); - return; - } - - // Handle svg element highlighting. - const p = findPolygon(elem); - if (p != null) { - if (set) { - origFill.set(p, p.style.fill); - p.style.fill = '#ccccff'; - } else if (origFill.has(p)) { - p.style.fill = origFill.get(p); - } - } - } - - function findPolygon(elem) { - if (elem.localName == 'polygon') return elem; - for (const c of elem.children) { - const p = findPolygon(c); - if (p != null) return p; - } - return null; - } - - // convert a string to a regexp that matches that string. - function quotemeta(str) { - return str.replace(/([\\\.?+*\[\](){}|^$])/g, '\\$1'); - } - - function setSampleIndexLink(id) { - const elem = document.getElementById(id); - if (elem != null) { - setHrefParams(elem, function (params) { - params.set("si", id); - }); - } - } - - // Update id's href to reflect current selection whenever it is - // liable to be followed. - function makeSearchLinkDynamic(id) { - const elem = document.getElementById(id); - if (elem == null) return; - - // Most links copy current selection into the 'f' parameter, - // but Refine menu links are different. - let param = 'f'; - if (id == 'ignore') param = 'i'; - if (id == 'hide') param = 'h'; - if (id == 'show') param = 's'; - if (id == 'show-from') param = 'sf'; - - // We update on mouseenter so middle-click/right-click work properly. - elem.addEventListener('mouseenter', updater); - elem.addEventListener('touchstart', updater); - - function updater() { - // The selection can be in one of two modes: regexp-based or - // list-based. Construct regular expression depending on mode. - let re = regexpActive - ? search.value - : Array.from(selected.keys()).map(key => quotemeta(nodes[key])).join('|'); - - setHrefParams(elem, function (params) { - if (re != '') { - // For focus/show/show-from, forget old parameter. For others, add to re. - if (param != 'f' && param != 's' && param != 'sf' && params.has(param)) { - const old = params.get(param); - if (old != '') { - re += '|' + old; - } - } - params.set(param, re); - } else { - params.delete(param); - } - }); - } - } - - function setHrefParams(elem, paramSetter) { - let url = new URL(elem.href); - url.hash = ''; - - // Copy params from this page's URL. - const params = url.searchParams; - for (const p of new URLSearchParams(window.location.search)) { - params.set(p[0], p[1]); - } - - // Give the params to the setter to modify. - paramSetter(params); - - elem.href = url.toString(); - } - - function handleTopClick(e) { - // Walk back until we find TR and then get the Name column (index 5) - let elem = e.target; - while (elem != null && elem.nodeName != 'TR') { - elem = elem.parentElement; - } - if (elem == null || elem.children.length < 6) return; - - e.preventDefault(); - const tr = elem; - const td = elem.children[5]; - if (td.nodeName != 'TD') return; - const name = td.innerText; - const index = nodes.indexOf(name); - if (index < 0) return; - - // Disable regexp mode. - regexpActive = false; - - if (selected.has(index)) { - unselect(index, elem); - } else { - select(index, elem); - } - updateButtons(); - } - - function updateButtons() { - const enable = (search.value != '' || selected.size != 0); - if (buttonsEnabled == enable) return; - buttonsEnabled = enable; - for (const id of ['focus', 'ignore', 'hide', 'show', 'show-from']) { - const link = document.getElementById(id); - if (link != null) { - link.classList.toggle('disabled', !enable); - } - } - } - - // Initialize button states - updateButtons(); - - // Setup event handlers - initMenus(); - if (svg != null) { - initPanAndZoom(svg, toggleSvgSelect); - } - if (toptable != null) { - toptable.addEventListener('mousedown', handleTopClick); - toptable.addEventListener('touchstart', handleTopClick); - } - - const ids = ['topbtn', 'graphbtn', 'flamegraph', 'peek', 'list', 'disasm', - 'focus', 'ignore', 'hide', 'show', 'show-from']; - ids.forEach(makeSearchLinkDynamic); - - const sampleIDs = [{{range .SampleTypes}}'{{.}}', {{end}}]; - sampleIDs.forEach(setSampleIndexLink); - - // Bind action to button with specified id. - function addAction(id, action) { - const btn = document.getElementById(id); - if (btn != null) { - btn.addEventListener('click', action); - btn.addEventListener('touchstart', action); - } - } - - addAction('details', handleDetails); - initConfigManager(); - - search.addEventListener('input', handleSearch); - search.addEventListener('keydown', handleKey); - - // Give initial focus to main container so it can be scrolled using keys. - const main = document.getElementById('bodycontainer'); - if (main) { - main.focus(); - } -} -</script> -{{end}} - -{{define "top" -}} -<!DOCTYPE html> -<html> -<head> - <meta charset="utf-8"> - <title>{{.Title}}</title> - {{template "css" .}} - <style type="text/css"> - </style> -</head> -<body> - {{template "header" .}} - <div id="top"> - <table id="toptable"> - <thead> - <tr> - <th id="flathdr1">Flat</th> - <th id="flathdr2">Flat%</th> - <th>Sum%</th> - <th id="cumhdr1">Cum</th> - <th id="cumhdr2">Cum%</th> - <th id="namehdr">Name</th> - <th>Inlined?</th> - </tr> - </thead> - <tbody id="rows"></tbody> - </table> - </div> - {{template "script" .}} - <script> - function makeTopTable(total, entries) { - const rows = document.getElementById('rows'); - if (rows == null) return; - - // Store initial index in each entry so we have stable node ids for selection. - for (let i = 0; i < entries.length; i++) { - entries[i].Id = 'node' + i; - } - - // Which column are we currently sorted by and in what order? - let currentColumn = ''; - let descending = false; - sortBy('Flat'); - - function sortBy(column) { - // Update sort criteria - if (column == currentColumn) { - descending = !descending; // Reverse order - } else { - currentColumn = column; - descending = (column != 'Name'); - } - - // Sort according to current criteria. - function cmp(a, b) { - const av = a[currentColumn]; - const bv = b[currentColumn]; - if (av < bv) return -1; - if (av > bv) return +1; - return 0; - } - entries.sort(cmp); - if (descending) entries.reverse(); - - function addCell(tr, val) { - const td = document.createElement('td'); - td.textContent = val; - tr.appendChild(td); - } - - function percent(v) { - return (v * 100.0 / total).toFixed(2) + '%'; - } - - // Generate rows - const fragment = document.createDocumentFragment(); - let sum = 0; - for (const row of entries) { - const tr = document.createElement('tr'); - tr.id = row.Id; - sum += row.Flat; - addCell(tr, row.FlatFormat); - addCell(tr, percent(row.Flat)); - addCell(tr, percent(sum)); - addCell(tr, row.CumFormat); - addCell(tr, percent(row.Cum)); - addCell(tr, row.Name); - addCell(tr, row.InlineLabel); - fragment.appendChild(tr); - } - - rows.textContent = ''; // Remove old rows - rows.appendChild(fragment); - } - - // Make different column headers trigger sorting. - function bindSort(id, column) { - const hdr = document.getElementById(id); - if (hdr == null) return; - const fn = function() { sortBy(column) }; - hdr.addEventListener('click', fn); - hdr.addEventListener('touch', fn); - } - bindSort('flathdr1', 'Flat'); - bindSort('flathdr2', 'Flat'); - bindSort('cumhdr1', 'Cum'); - bindSort('cumhdr2', 'Cum'); - bindSort('namehdr', 'Name'); - } - - viewer(new URL(window.location.href), {{.Nodes}}); - makeTopTable({{.Total}}, {{.Top}}); - </script> -</body> -</html> -{{end}} - -{{define "sourcelisting" -}} -<!DOCTYPE html> -<html> -<head> - <meta charset="utf-8"> - <title>{{.Title}}</title> - {{template "css" .}} - {{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> -</html> -{{end}} - -{{define "plaintext" -}} -<!DOCTYPE html> -<html> -<head> - <meta charset="utf-8"> - <title>{{.Title}}</title> - {{template "css" .}} -</head> -<body> - {{template "header" .}} - <div id="content"> - <pre> - {{.TextBody}} - </pre> - </div> - {{template "script" .}} - <script>viewer(new URL(window.location.href), null);</script> -</body> -</html> -{{end}} - -{{define "flamegraph" -}} -<!DOCTYPE html> -<html> -<head> - <meta charset="utf-8"> - <title>{{.Title}}</title> - {{template "css" .}} - <style type="text/css">{{template "d3flamegraphcss" .}}</style> - <style type="text/css"> - .flamegraph-content { - width: 90%; - min-width: 80%; - margin-left: 5%; - } - .flamegraph-details { - height: 1.2em; - width: 90%; - min-width: 90%; - margin-left: 5%; - padding: 15px 0 35px; - } - </style> -</head> -<body> - {{template "header" .}} - <div id="bodycontainer"> - <div id="flamegraphdetails" class="flamegraph-details"></div> - <div class="flamegraph-content"> - <div id="chart"></div> - </div> - </div> - {{template "script" .}} - <script>viewer(new URL(window.location.href), {{.Nodes}});</script> - <script>{{template "d3flamegraphscript" .}}</script> - <script> - var data = {{.FlameGraph}}; - - var width = document.getElementById('chart').clientWidth; - - var flameGraph = flamegraph() - .width(width) - .cellHeight(18) - .minFrameSize(1) - .transitionDuration(750) - .inverted(true) - .sort(true) - .title('') - .tooltip(false) - .setDetailsElement(document.getElementById('flamegraphdetails')); - - // <full name> (percentage, value) - flameGraph.label((d) => d.data.f + ' (' + d.data.p + ', ' + d.data.l + ')'); - - flameGraph.setColorHue('warm'); - - select('#chart') - .datum(data) - .call(flameGraph); - - function clear() { - flameGraph.clear(); - } - - function resetZoom() { - flameGraph.resetZoom(); - } - - window.addEventListener('resize', function() { - var width = document.getElementById('chart').clientWidth; - var graphs = document.getElementsByClassName('d3-flame-graph'); - if (graphs.length > 0) { - graphs[0].setAttribute('width', width); - } - flameGraph.width(width); - flameGraph.resetZoom(); - }, true); - - var search = document.getElementById('search'); - var searchAlarm = null; - - function selectMatching() { - searchAlarm = null; + // Load specified file. + loadFile := func(fname string) string { + data, err := embeddedFiles.ReadFile(fname) + if err != nil { + fmt.Fprintf(os.Stderr, "internal/driver: embedded file %q not found\n", + fname) + os.Exit(1) + } + return string(data) + } + loadCSS := func(fname string) string { + return `<style type="text/css">` + "\n" + loadFile(fname) + `</style>` + "\n" + } + loadJS := func(fname string) string { + return `<script>` + "\n" + loadFile(fname) + `</script>` + "\n" + } - if (search.value != '') { - flameGraph.search(search.value); - } else { - flameGraph.clear(); - } - } + // Define a named template with specified contents. + def := func(name, contents string) { + sub := template.New(name) + template.Must(sub.Parse(contents)) + template.Must(templates.AddParseTree(name, sub.Tree)) + } - function handleSearch() { - // Delay expensive processing so a flurry of key strokes is handled once. - if (searchAlarm != null) { - clearTimeout(searchAlarm); - } - searchAlarm = setTimeout(selectMatching, 300); - } + // Pre-packaged third-party files. + def("d3flamegraphscript", d3flamegraph.JSSource) + def("d3flamegraphcss", d3flamegraph.CSSSource) - search.addEventListener('input', handleSearch); - </script> -</body> -</html> -{{end}} -`)) + // Embeded files. + def("css", loadCSS("html/common.css")) + def("header", loadFile("html/header.html")) + def("graph", loadFile("html/graph.html")) + def("script", loadJS("html/common.js")) + def("top", loadFile("html/top.html")) + def("sourcelisting", loadFile("html/source.html")) + def("plaintext", loadFile("html/plaintext.html")) + def("flamegraph", loadFile("html/flamegraph.html")) } diff --git a/src/cmd/vendor/github.com/google/pprof/internal/measurement/measurement.go b/src/cmd/vendor/github.com/google/pprof/internal/measurement/measurement.go index 53325740a3..b5fcfbc3e4 100644 --- a/src/cmd/vendor/github.com/google/pprof/internal/measurement/measurement.go +++ b/src/cmd/vendor/github.com/google/pprof/internal/measurement/measurement.go @@ -110,10 +110,15 @@ func compatibleValueTypes(v1, v2 *profile.ValueType) bool { return false } - return v1.Unit == v2.Unit || - (timeUnits.sniffUnit(v1.Unit) != nil && timeUnits.sniffUnit(v2.Unit) != nil) || - (memoryUnits.sniffUnit(v1.Unit) != nil && memoryUnits.sniffUnit(v2.Unit) != nil) || - (gcuUnits.sniffUnit(v1.Unit) != nil && gcuUnits.sniffUnit(v2.Unit) != nil) + if v1.Unit == v2.Unit { + return true + } + for _, ut := range unitTypes { + if ut.sniffUnit(v1.Unit) != nil && ut.sniffUnit(v2.Unit) != nil { + return true + } + } + return false } // Scale a measurement from an unit to a different unit and returns @@ -125,14 +130,10 @@ func Scale(value int64, fromUnit, toUnit string) (float64, string) { v, u := Scale(-value, fromUnit, toUnit) return -v, u } - if m, u, ok := memoryUnits.convertUnit(value, fromUnit, toUnit); ok { - return m, u - } - if t, u, ok := timeUnits.convertUnit(value, fromUnit, toUnit); ok { - return t, u - } - if g, u, ok := gcuUnits.convertUnit(value, fromUnit, toUnit); ok { - return g, u + for _, ut := range unitTypes { + if v, u, ok := ut.convertUnit(value, fromUnit, toUnit); ok { + return v, u + } } // Skip non-interesting units. switch toUnit { @@ -257,7 +258,7 @@ func (ut unitType) convertUnit(value int64, fromUnitStr, toUnitStr string) (floa return v / toUnit.factor, toUnit.canonicalName, true } -var memoryUnits = unitType{ +var unitTypes = []unitType{{ units: []unit{ {"B", []string{"b", "byte"}, 1}, {"kB", []string{"kb", "kbyte", "kilobyte"}, float64(1 << 10)}, @@ -267,9 +268,7 @@ var memoryUnits = unitType{ {"PB", []string{"pb", "pbyte", "petabyte"}, float64(1 << 50)}, }, defaultUnit: unit{"B", []string{"b", "byte"}, 1}, -} - -var timeUnits = unitType{ +}, { units: []unit{ {"ns", []string{"ns", "nanosecond"}, float64(time.Nanosecond)}, {"us", []string{"μs", "us", "microsecond"}, float64(time.Microsecond)}, @@ -278,9 +277,7 @@ var timeUnits = unitType{ {"hrs", []string{"hour", "hr"}, float64(time.Hour)}, }, defaultUnit: unit{"s", []string{}, float64(time.Second)}, -} - -var gcuUnits = unitType{ +}, { units: []unit{ {"n*GCU", []string{"nanogcu"}, 1e-9}, {"u*GCU", []string{"microgcu"}, 1e-6}, @@ -293,4 +290,4 @@ var gcuUnits = unitType{ {"P*GCU", []string{"petagcu"}, 1e15}, }, defaultUnit: unit{"GCU", []string{}, 1.0}, -} +}} diff --git a/src/cmd/vendor/github.com/google/pprof/profile/legacy_profile.go b/src/cmd/vendor/github.com/google/pprof/profile/legacy_profile.go index 0c8f3bb5b7..9ba9a77c92 100644 --- a/src/cmd/vendor/github.com/google/pprof/profile/legacy_profile.go +++ b/src/cmd/vendor/github.com/google/pprof/profile/legacy_profile.go @@ -295,11 +295,12 @@ func get64b(b []byte) (uint64, []byte) { // // The general format for profilez samples is a sequence of words in // binary format. The first words are a header with the following data: -// 1st word -- 0 -// 2nd word -- 3 -// 3rd word -- 0 if a c++ application, 1 if a java application. -// 4th word -- Sampling period (in microseconds). -// 5th word -- Padding. +// +// 1st word -- 0 +// 2nd word -- 3 +// 3rd word -- 0 if a c++ application, 1 if a java application. +// 4th word -- Sampling period (in microseconds). +// 5th word -- Padding. func parseCPU(b []byte) (*Profile, error) { var parse func([]byte) (uint64, []byte) var n1, n2, n3, n4, n5 uint64 @@ -403,15 +404,18 @@ func cleanupDuplicateLocations(p *Profile) { // // profilez samples are a repeated sequence of stack frames of the // form: -// 1st word -- The number of times this stack was encountered. -// 2nd word -- The size of the stack (StackSize). -// 3rd word -- The first address on the stack. -// ... -// StackSize + 2 -- The last address on the stack +// +// 1st word -- The number of times this stack was encountered. +// 2nd word -- The size of the stack (StackSize). +// 3rd word -- The first address on the stack. +// ... +// StackSize + 2 -- The last address on the stack +// // The last stack trace is of the form: -// 1st word -- 0 -// 2nd word -- 1 -// 3rd word -- 0 +// +// 1st word -- 0 +// 2nd word -- 1 +// 3rd word -- 0 // // Addresses from stack traces may point to the next instruction after // each call. Optionally adjust by -1 to land somewhere on the actual diff --git a/src/cmd/vendor/github.com/google/pprof/third_party/d3flamegraph/README.md b/src/cmd/vendor/github.com/google/pprof/third_party/d3flamegraph/README.md index 94055d2846..eb84b68007 100644 --- a/src/cmd/vendor/github.com/google/pprof/third_party/d3flamegraph/README.md +++ b/src/cmd/vendor/github.com/google/pprof/third_party/d3flamegraph/README.md @@ -25,7 +25,7 @@ A demonstration of building a custom D3 4.0 bundle using ES2015 modules and Roll ## Old version of d3-pprof -A previous verison of d3-flame-graph bundled for pprof used Rollup instead of +A previous version of d3-flame-graph bundled for pprof used Rollup instead of Webpack. This has now been migrated directly into this directory. The repository configuring Rollup was here: diff --git a/src/cmd/vendor/github.com/ianlancetaylor/demangle/ast.go b/src/cmd/vendor/github.com/ianlancetaylor/demangle/ast.go index 7b9178f1bb..20d8a9982e 100644 --- a/src/cmd/vendor/github.com/ianlancetaylor/demangle/ast.go +++ b/src/cmd/vendor/github.com/ianlancetaylor/demangle/ast.go @@ -1451,6 +1451,34 @@ func (ft *FixedType) goString(indent int, field string) string { ft.Base.goString(indent+2, "Base: ")) } +// BinaryFP is a binary floating-point type. +type BinaryFP struct { + Bits int +} + +func (bfp *BinaryFP) print(ps *printState) { + fmt.Fprintf(&ps.buf, "_Float%d", bfp.Bits) +} + +func (bfp *BinaryFP) Traverse(fn func(AST) bool) { + fn(bfp) +} + +func (bfp *BinaryFP) Copy(fn func(AST) AST, skip func(AST) bool) AST { + if skip(bfp) { + return nil + } + return fn(bfp) +} + +func (bfp *BinaryFP) GoString() string { + return bfp.goString(0, "") +} + +func (bfp *BinaryFP) goString(indent int, field string) string { + return fmt.Sprintf("%*s%sBinaryFP: %d", indent, "", field, bfp.Bits) +} + // VectorType is a vector type. type VectorType struct { Dimension AST @@ -2492,6 +2520,7 @@ func (u *Unary) print(ps *printState) { } if !u.Suffix { + isDelete := op != nil && (op.Name == "delete " || op.Name == "delete[] ") if op != nil && op.Name == "::" { // Don't use parentheses after ::. ps.print(expr) @@ -2506,11 +2535,11 @@ func (u *Unary) print(ps *printState) { ps.print(expr) ps.writeByte(')') } else if ps.llvmStyle { - if op == nil || op.Name != `operator"" ` { + if op == nil || (op.Name != `operator"" ` && !isDelete) { ps.writeByte('(') } ps.print(expr) - if op == nil || op.Name != `operator"" ` { + if op == nil || (op.Name != `operator"" ` && !isDelete) { ps.writeByte(')') } } else { @@ -2653,6 +2682,9 @@ func (b *Binary) print(ps *printState) { case ".", "->": skipBothParens = true addSpaces = false + case "->*": + skipParens = true + addSpaces = false } } @@ -3115,8 +3147,20 @@ type New struct { } func (n *New) print(ps *printState) { - // Op doesn't really matter for printing--we always print "new". - ps.writeString("new ") + if !ps.llvmStyle { + // Op doesn't really matter for printing--we always print "new". + ps.writeString("new ") + } else { + op, _ := n.Op.(*Operator) + if op != nil { + ps.writeString(op.Name) + if n.Place == nil { + ps.writeByte(' ') + } + } else { + ps.print(n.Op) + } + } if n.Place != nil { parenthesize(ps, n.Place) ps.writeByte(' ') diff --git a/src/cmd/vendor/github.com/ianlancetaylor/demangle/demangle.go b/src/cmd/vendor/github.com/ianlancetaylor/demangle/demangle.go index 9eec0aa3c8..66ac7dde62 100644 --- a/src/cmd/vendor/github.com/ianlancetaylor/demangle/demangle.go +++ b/src/cmd/vendor/github.com/ianlancetaylor/demangle/demangle.go @@ -73,19 +73,26 @@ func ToString(name string, options ...Option) (string, error) { // Check for an old-style Rust mangled name. // It starts with _ZN and ends with "17h" followed by 16 hex digits - // followed by "E". - if strings.HasPrefix(name, "_ZN") && strings.HasSuffix(name, "E") && len(name) > 23 && name[len(name)-20:len(name)-17] == "17h" { - noRust := false - for _, o := range options { - if o == NoRust { - noRust = true - break - } + // followed by "E" followed by an optional suffix starting with "." + // (which we ignore). + if strings.HasPrefix(name, "_ZN") { + rname := name + if pos := strings.LastIndex(rname, "E."); pos > 0 { + rname = rname[:pos+1] } - if !noRust { - s, ok := oldRustToString(name, options) - if ok { - return s, nil + if strings.HasSuffix(rname, "E") && len(rname) > 23 && rname[len(rname)-20:len(rname)-17] == "17h" { + noRust := false + for _, o := range options { + if o == NoRust { + noRust = true + break + } + } + if !noRust { + s, ok := oldRustToString(rname, options) + if ok { + return s, nil + } } } } @@ -332,9 +339,11 @@ const ( notForLocalName ) -// encoding ::= <(function) name> <bare-function-type> -// <(data) name> -// <special-name> +// encoding parses: +// +// encoding ::= <(function) name> <bare-function-type> +// <(data) name> +// <special-name> func (st *state) encoding(params bool, local forLocalNameType) AST { if len(st.str) < 1 { st.fail("expected encoding") @@ -499,7 +508,9 @@ func isCDtorConversion(a AST) bool { } } -// <tagged-name> ::= <name> B <source-name> +// taggedName parses: +// +// <tagged-name> ::= <name> B <source-name> func (st *state) taggedName(a AST) AST { for len(st.str) > 0 && st.str[0] == 'B' { st.advance(1) @@ -509,16 +520,18 @@ func (st *state) taggedName(a AST) AST { return a } -// <name> ::= <nested-name> -// ::= <unscoped-name> -// ::= <unscoped-template-name> <template-args> -// ::= <local-name> +// name parses: +// +// <name> ::= <nested-name> +// ::= <unscoped-name> +// ::= <unscoped-template-name> <template-args> +// ::= <local-name> // -// <unscoped-name> ::= <unqualified-name> -// ::= St <unqualified-name> +// <unscoped-name> ::= <unqualified-name> +// ::= St <unqualified-name> // -// <unscoped-template-name> ::= <unscoped-name> -// ::= <substitution> +// <unscoped-template-name> ::= <unscoped-name> +// ::= <substitution> func (st *state) name() AST { if len(st.str) < 1 { st.fail("expected name") @@ -593,8 +606,10 @@ func (st *state) name() AST { } } -// <nested-name> ::= N [<CV-qualifiers>] [<ref-qualifier>] <prefix> <unqualified-name> E -// ::= N [<CV-qualifiers>] [<ref-qualifier>] <template-prefix> <template-args> E +// 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 { st.checkChar('N') q := st.cvQualifiers() @@ -610,19 +625,21 @@ func (st *state) nestedName() AST { return a } -// <prefix> ::= <prefix> <unqualified-name> -// ::= <template-prefix> <template-args> -// ::= <template-param> -// ::= <decltype> -// ::= -// ::= <substitution> +// prefix parses: // -// <template-prefix> ::= <prefix> <(template) unqualified-name> -// ::= <template-param> -// ::= <substitution> +// <prefix> ::= <prefix> <unqualified-name> +// ::= <template-prefix> <template-args> +// ::= <template-param> +// ::= <decltype> +// ::= +// ::= <substitution> // -// <decltype> ::= Dt <expression> E -// ::= DT <expression> E +// <template-prefix> ::= <prefix> <(template) unqualified-name> +// ::= <template-param> +// ::= <substitution> +// +// <decltype> ::= Dt <expression> E +// ::= DT <expression> E func (st *state) prefix() AST { var a AST @@ -777,12 +794,14 @@ func (st *state) prefix() AST { } } -// <unqualified-name> ::= <operator-name> -// ::= <ctor-dtor-name> -// ::= <source-name> -// ::= <local-source-name> +// unqualifiedName parses: +// +// <unqualified-name> ::= <operator-name> +// ::= <ctor-dtor-name> +// ::= <source-name> +// ::= <local-source-name> // -// <local-source-name> ::= L <source-name> <discriminator> +// <local-source-name> ::= L <source-name> <discriminator> func (st *state) unqualifiedName() (r AST, isCast bool) { if len(st.str) < 1 { st.fail("expected unqualified name") @@ -852,8 +871,10 @@ func (st *state) unqualifiedName() (r AST, isCast bool) { return a, isCast } -// <source-name> ::= <(positive length) number> <identifier> -// identifier ::= <(unqualified source code identifier)> +// sourceName parses: +// +// <source-name> ::= <(positive length) number> <identifier> +// identifier ::= <(unqualified source code identifier)> func (st *state) sourceName() AST { val := st.number() if val <= 0 { @@ -880,7 +901,9 @@ func (st *state) sourceName() AST { return n } -// number ::= [n] <(non-negative decimal integer)> +// number parses: +// +// number ::= [n] <(non-negative decimal integer)> func (st *state) number() int { neg := false if len(st.str) > 0 && st.str[0] == 'n' { @@ -906,7 +929,9 @@ func (st *state) number() int { return val } -// <seq-id> ::= <0-9A-Z>+ +// seqID parses: +// +// <seq-id> ::= <0-9A-Z>+ // // We expect this to be followed by an underscore. func (st *state) seqID(eofOK bool) int { @@ -1030,9 +1055,11 @@ var operators = map[string]operator{ "tw": {"throw ", 1}, } -// operator_name ::= many different two character encodings. -// ::= cv <type> -// ::= v <digit> <source-name> +// operatorName parses: +// +// operator_name ::= many different two character encodings. +// ::= cv <type> +// ::= v <digit> <source-name> // // We need to know whether we are in an expression because it affects // how we handle template parameters in the type of a cast operator. @@ -1068,9 +1095,11 @@ func (st *state) operatorName(inExpression bool) (AST, int) { } } -// <local-name> ::= Z <(function) encoding> E <(entity) name> [<discriminator>] -// ::= Z <(function) encoding> E s [<discriminator>] -// ::= Z <(function) encoding> E d [<parameter> number>] _ <entity name> +// localName parses: +// +// <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 { st.checkChar('Z') fn := st.encoding(true, forLocalName) @@ -1139,23 +1168,25 @@ func (st *state) javaResource() AST { return &Special{Prefix: "java resource ", Val: &Name{Name: final}} } -// <special-name> ::= TV <type> -// ::= TT <type> -// ::= TI <type> -// ::= TS <type> -// ::= TA <template-arg> -// ::= GV <(object) name> -// ::= T <call-offset> <(base) encoding> -// ::= Tc <call-offset> <call-offset> <(base) encoding> -// Also g++ extensions: -// ::= TC <type> <(offset) number> _ <(base) type> -// ::= TF <type> -// ::= TJ <type> -// ::= GR <name> -// ::= GA <encoding> -// ::= Gr <resource name> -// ::= GTt <encoding> -// ::= GTn <encoding> +// specialName parses: +// +// <special-name> ::= TV <type> +// ::= TT <type> +// ::= TI <type> +// ::= TS <type> +// ::= TA <template-arg> +// ::= GV <(object) name> +// ::= T <call-offset> <(base) encoding> +// ::= Tc <call-offset> <call-offset> <(base) encoding> +// g++ extensions: +// ::= TC <type> <(offset) number> _ <(base) type> +// ::= TF <type> +// ::= TJ <type> +// ::= GR <name> +// ::= GA <encoding> +// ::= Gr <resource name> +// ::= GTt <encoding> +// ::= GTn <encoding> func (st *state) specialName() AST { if st.str[0] == 'T' { st.advance(1) @@ -1268,12 +1299,14 @@ func (st *state) specialName() AST { } } -// <call-offset> ::= h <nv-offset> _ -// ::= v <v-offset> _ +// callOffset parses: +// +// <call-offset> ::= h <nv-offset> _ +// ::= v <v-offset> _ // -// <nv-offset> ::= <(offset) number> +// <nv-offset> ::= <(offset) number> // -// <v-offset> ::= <(offset) number> _ <(virtual offset) number> +// <v-offset> ::= <(offset) number> _ <(virtual offset) number> // // The c parameter, if not 0, is a character we just read which is the // start of the <call-offset>. @@ -1331,24 +1364,26 @@ var builtinTypes = map[byte]string{ 'z': "...", } -// <type> ::= <builtin-type> -// ::= <function-type> -// ::= <class-enum-type> -// ::= <array-type> -// ::= <pointer-to-member-type> -// ::= <template-param> -// ::= <template-template-param> <template-args> -// ::= <substitution> -// ::= <CV-qualifiers> <type> -// ::= P <type> -// ::= R <type> -// ::= O <type> (C++0x) -// ::= C <type> -// ::= G <type> -// ::= U <source-name> <type> +// demangleType parses: // -// <builtin-type> ::= various one letter codes -// ::= u <source-name> +// <type> ::= <builtin-type> +// ::= <function-type> +// ::= <class-enum-type> +// ::= <array-type> +// ::= <pointer-to-member-type> +// ::= <template-param> +// ::= <template-template-param> <template-args> +// ::= <substitution> +// ::= <CV-qualifiers> <type> +// ::= P <type> +// ::= R <type> +// ::= O <type> (C++0x) +// ::= C <type> +// ::= G <type> +// ::= U <source-name> <type> +// +// <builtin-type> ::= various one letter codes +// ::= u <source-name> func (st *state) demangleType(isCast bool) AST { if len(st.str) == 0 { st.fail("expected type") @@ -1544,24 +1579,32 @@ func (st *state) demangleType(isCast bool) AST { case 'F': accum := false + bits := 0 if len(st.str) > 0 && isDigit(st.str[0]) { accum = true - // We don't care about the bits. - _ = st.number() - } - base := st.demangleType(isCast) - if len(st.str) > 0 && isDigit(st.str[0]) { - // We don't care about the bits. - st.number() + bits = st.number() } - sat := false - if len(st.str) > 0 { - if st.str[0] == 's' { - sat = true + if len(st.str) > 0 && st.str[0] == '_' { + if bits == 0 { + st.fail("expected non-zero number of bits") } st.advance(1) + ret = &BinaryFP{Bits: bits} + } else { + base := st.demangleType(isCast) + if len(st.str) > 0 && isDigit(st.str[0]) { + // We don't care about the bits. + st.number() + } + sat := false + if len(st.str) > 0 { + if st.str[0] == 's' { + sat = true + } + st.advance(1) + } + ret = &FixedType{Base: base, Accum: accum, Sat: sat} } - ret = &FixedType{Base: base, Accum: accum, Sat: sat} case 'v': ret = st.vectorType(isCast) @@ -1615,25 +1658,25 @@ func (st *state) demangleType(isCast bool) AST { // is if there is another set of template-args immediately after this // set. That would look like this: // -// <nested-name> -// -> <template-prefix> <template-args> -// -> <prefix> <template-unqualified-name> <template-args> -// -> <unqualified-name> <template-unqualified-name> <template-args> -// -> <source-name> <template-unqualified-name> <template-args> -// -> <source-name> <operator-name> <template-args> -// -> <source-name> cv <type> <template-args> -// -> <source-name> cv <template-template-param> <template-args> <template-args> +// <nested-name> +// -> <template-prefix> <template-args> +// -> <prefix> <template-unqualified-name> <template-args> +// -> <unqualified-name> <template-unqualified-name> <template-args> +// -> <source-name> <template-unqualified-name> <template-args> +// -> <source-name> <operator-name> <template-args> +// -> <source-name> cv <type> <template-args> +// -> <source-name> cv <template-template-param> <template-args> <template-args> // // Otherwise, we have this derivation: // -// <nested-name> -// -> <template-prefix> <template-args> -// -> <prefix> <template-unqualified-name> <template-args> -// -> <unqualified-name> <template-unqualified-name> <template-args> -// -> <source-name> <template-unqualified-name> <template-args> -// -> <source-name> <operator-name> <template-args> -// -> <source-name> cv <type> <template-args> -// -> <source-name> cv <template-param> <template-args> +// <nested-name> +// -> <template-prefix> <template-args> +// -> <prefix> <template-unqualified-name> <template-args> +// -> <unqualified-name> <template-unqualified-name> <template-args> +// -> <source-name> <template-unqualified-name> <template-args> +// -> <source-name> <operator-name> <template-args> +// -> <source-name> cv <type> <template-args> +// -> <source-name> cv <template-param> <template-args> // // in which the template-args are actually part of the prefix. For // the special case where this arises, demangleType is called with @@ -1710,7 +1753,9 @@ var qualifiers = map[byte]string{ 'K': "const", } -// <CV-qualifiers> ::= [r] [V] [K] +// cvQualifiers parses: +// +// <CV-qualifiers> ::= [r] [V] [K] func (st *state) cvQualifiers() AST { var q []AST qualLoop: @@ -1758,8 +1803,10 @@ qualLoop: return &Qualifiers{Qualifiers: q} } -// <ref-qualifier> ::= R -// ::= O +// refQualifier parses: +// +// <ref-qualifier> ::= R +// ::= O func (st *state) refQualifier() string { if len(st.str) > 0 { switch st.str[0] { @@ -1774,7 +1821,9 @@ func (st *state) refQualifier() string { return "" } -// <type>+ +// parmlist parses: +// +// <type>+ func (st *state) parmlist() []AST { var ret []AST for { @@ -1809,7 +1858,9 @@ func (st *state) parmlist() []AST { return ret } -// <function-type> ::= F [Y] <bare-function-type> [<ref-qualifier>] E +// functionType parses: +// +// <function-type> ::= F [Y] <bare-function-type> [<ref-qualifier>] E func (st *state) functionType() AST { st.checkChar('F') if len(st.str) > 0 && st.str[0] == 'Y' { @@ -1828,7 +1879,9 @@ func (st *state) functionType() AST { return ret } -// <bare-function-type> ::= [J]<type>+ +// bareFunctionType parses: +// +// <bare-function-type> ::= [J]<type>+ func (st *state) bareFunctionType(hasReturnType bool) AST { if len(st.str) > 0 && st.str[0] == 'J' { hasReturnType = true @@ -1846,8 +1899,10 @@ func (st *state) bareFunctionType(hasReturnType bool) AST { } } -// <array-type> ::= A <(positive dimension) number> _ <(element) type> -// ::= A [<(dimension) expression>] _ <(element) type> +// arrayType parses: +// +// <array-type> ::= A <(positive dimension) number> _ <(element) type> +// ::= A [<(dimension) expression>] _ <(element) type> func (st *state) arrayType(isCast bool) AST { st.checkChar('A') @@ -1887,8 +1942,10 @@ func (st *state) arrayType(isCast bool) AST { return arr } -// <vector-type> ::= Dv <number> _ <type> -// ::= Dv _ <expression> _ <type> +// vectorType parses: +// +// <vector-type> ::= Dv <number> _ <type> +// ::= Dv _ <expression> _ <type> func (st *state) vectorType(isCast bool) AST { if len(st.str) == 0 { st.fail("expected vector dimension") @@ -1913,7 +1970,9 @@ func (st *state) vectorType(isCast bool) AST { return &VectorType{Dimension: dim, Base: t} } -// <pointer-to-member-type> ::= M <(class) type> <(member) type> +// pointerToMemberType parses: +// +// <pointer-to-member-type> ::= M <(class) type> <(member) type> func (st *state) pointerToMemberType(isCast bool) AST { st.checkChar('M') cl := st.demangleType(false) @@ -1939,7 +1998,9 @@ func (st *state) pointerToMemberType(isCast bool) AST { return &PtrMem{Class: cl, Member: mem} } -// <non-negative number> _ */ +// compactNumber parses: +// +// <non-negative number> _ func (st *state) compactNumber() int { if len(st.str) == 0 { st.fail("missing index") @@ -1958,10 +2019,12 @@ func (st *state) compactNumber() int { return n + 1 } -// <template-param> ::= T_ -// ::= T <(parameter-2 non-negative) number> _ -// ::= TL <level-1> __ -// ::= TL <level-1> _ <parameter-2 non-negative number> _ +// templateParam parses: +// +// <template-param> ::= T_ +// ::= T <(parameter-2 non-negative) number> _ +// ::= TL <level-1> __ +// ::= TL <level-1> _ <parameter-2 non-negative number> _ // // When a template parameter is a substitution candidate, any // reference to that substitution refers to the template parameter @@ -2058,7 +2121,9 @@ func (st *state) clearTemplateArgs(args []AST) { } } -// <template-args> ::= I <template-arg>+ E +// templateArgs parses: +// +// <template-args> ::= I <template-arg>+ E func (st *state) templateArgs() []AST { if len(st.str) == 0 || (st.str[0] != 'I' && st.str[0] != 'J') { panic("internal error") @@ -2074,9 +2139,11 @@ func (st *state) templateArgs() []AST { return ret } -// <template-arg> ::= <type> -// ::= X <expression> E -// ::= <expr-primary> +// templateArg parses: +// +// <template-arg> ::= <type> +// ::= X <expression> E +// ::= <expr-primary> func (st *state) templateArg() AST { if len(st.str) == 0 { st.fail("missing template argument") @@ -2122,66 +2189,67 @@ func (st *state) exprList(stop byte) AST { return &ExprList{Exprs: exprs} } -// <expression> ::= <(unary) operator-name> <expression> -// ::= <(binary) operator-name> <expression> <expression> -// ::= <(trinary) operator-name> <expression> <expression> <expression> -// ::= pp_ <expression> -// ::= mm_ <expression> -// ::= cl <expression>+ E -// ::= cl <expression>+ E -// ::= cv <type> <expression> -// ::= cv <type> _ <expression>* E -// ::= tl <type> <braced-expression>* E -// ::= il <braced-expression>* E -// ::= [gs] nw <expression>* _ <type> E -// ::= [gs] nw <expression>* _ <type> <initializer> -// ::= [gs] na <expression>* _ <type> E -// ::= [gs] na <expression>* _ <type> <initializer> -// ::= [gs] dl <expression> -// ::= [gs] da <expression> -// ::= dc <type> <expression> -// ::= sc <type> <expression> -// ::= cc <type> <expression> -// ::= mc <parameter type> <expr> [<offset number>] E -// ::= rc <type> <expression> -// ::= ti <type> -// ::= te <expression> -// ::= so <referent type> <expr> [<offset number>] <union-selector>* [p] E -// ::= st <type> -// ::= sz <expression> -// ::= at <type> -// ::= az <expression> -// ::= nx <expression> -// ::= <template-param> -// ::= <function-param> -// ::= dt <expression> <unresolved-name> -// ::= pt <expression> <unresolved-name> -// ::= ds <expression> <expression> -// ::= sZ <template-param> -// ::= sZ <function-param> -// ::= sP <template-arg>* E -// ::= sp <expression> -// ::= fl <binary operator-name> <expression> -// ::= fr <binary operator-name> <expression> -// ::= fL <binary operator-name> <expression> <expression> -// ::= fR <binary operator-name> <expression> <expression> -// ::= tw <expression> -// ::= tr -// ::= u <source-name> <template-arg>* E -// ::= <unresolved-name> -// ::= <expr-primary> +// expression parses: // -// <function-param> ::= fp <CV-qualifiers> _ -// ::= fp <CV-qualifiers> <number> -// ::= fL <number> p <CV-qualifiers> _ -// ::= fL <number> p <CV-qualifiers> <number> -// ::= fpT +// <expression> ::= <(unary) operator-name> <expression> +// ::= <(binary) operator-name> <expression> <expression> +// ::= <(trinary) operator-name> <expression> <expression> <expression> +// ::= pp_ <expression> +// ::= mm_ <expression> +// ::= cl <expression>+ E +// ::= cl <expression>+ E +// ::= cv <type> <expression> +// ::= cv <type> _ <expression>* E +// ::= tl <type> <braced-expression>* E +// ::= il <braced-expression>* E +// ::= [gs] nw <expression>* _ <type> E +// ::= [gs] nw <expression>* _ <type> <initializer> +// ::= [gs] na <expression>* _ <type> E +// ::= [gs] na <expression>* _ <type> <initializer> +// ::= [gs] dl <expression> +// ::= [gs] da <expression> +// ::= dc <type> <expression> +// ::= sc <type> <expression> +// ::= cc <type> <expression> +// ::= mc <parameter type> <expr> [<offset number>] E +// ::= rc <type> <expression> +// ::= ti <type> +// ::= te <expression> +// ::= so <referent type> <expr> [<offset number>] <union-selector>* [p] E +// ::= st <type> +// ::= sz <expression> +// ::= at <type> +// ::= az <expression> +// ::= nx <expression> +// ::= <template-param> +// ::= <function-param> +// ::= dt <expression> <unresolved-name> +// ::= pt <expression> <unresolved-name> +// ::= ds <expression> <expression> +// ::= sZ <template-param> +// ::= sZ <function-param> +// ::= sP <template-arg>* E +// ::= sp <expression> +// ::= fl <binary operator-name> <expression> +// ::= fr <binary operator-name> <expression> +// ::= fL <binary operator-name> <expression> <expression> +// ::= fR <binary operator-name> <expression> <expression> +// ::= tw <expression> +// ::= tr +// ::= u <source-name> <template-arg>* E +// ::= <unresolved-name> +// ::= <expr-primary> // -// <braced-expression> ::= <expression> -// ::= di <field source-name> <braced-expression> -// ::= dx <index expression> <braced-expression> -// ::= dX <range begin expression> <range end expression> <braced-expression> +// <function-param> ::= fp <CV-qualifiers> _ +// ::= fp <CV-qualifiers> <number> +// ::= fL <number> p <CV-qualifiers> _ +// ::= fL <number> p <CV-qualifiers> <number> +// ::= fpT // +// <braced-expression> ::= <expression> +// ::= di <field source-name> <braced-expression> +// ::= dx <index expression> <braced-expression> +// ::= dX <range begin expression> <range end expression> <braced-expression> func (st *state) expression() AST { if len(st.str) == 0 { st.fail("expected expression") @@ -2426,8 +2494,10 @@ func (st *state) expression() AST { } } -// <expression> ::= so <referent type> <expr> [<offset number>] <union-selector>* [p] E -// <union-selector> ::= _ [<number>] +// subobject parses: +// +// <expression> ::= so <referent type> <expr> [<offset number>] <union-selector>* [p] E +// <union-selector> ::= _ [<number>] func (st *state) subobject() AST { typ := st.demangleType(false) expr := st.expression() @@ -2462,10 +2532,12 @@ func (st *state) subobject() AST { } } -// <unresolved-name> ::= [gs] <base-unresolved-name> -// ::= sr <unresolved-type> <base-unresolved-name> -// ::= srN <unresolved-type> <unresolved-qualifier-level>+ E <base-unresolved-name> -// ::= [gs] sr <unresolved-qualifier-level>+ E <base-unresolved-name> +// unresolvedName parses: +// +// <unresolved-name> ::= [gs] <base-unresolved-name> +// ::= sr <unresolved-type> <base-unresolved-name> +// ::= srN <unresolved-type> <unresolved-qualifier-level>+ E <base-unresolved-name> +// ::= [gs] sr <unresolved-qualifier-level>+ E <base-unresolved-name> func (st *state) unresolvedName() AST { if len(st.str) >= 2 && st.str[:2] == "gs" { st.advance(2) @@ -2538,12 +2610,14 @@ func (st *state) unresolvedName() AST { } } -// <base-unresolved-name> ::= <simple-id> -// ::= on <operator-name> -// ::= on <operator-name> <template-args> -// ::= dn <destructor-name> +// baseUnresolvedName parses: +// +// <base-unresolved-name> ::= <simple-id> +// ::= on <operator-name> +// ::= on <operator-name> <template-args> +// ::= dn <destructor-name> // -//<simple-id> ::= <source-name> [ <template-args> ] +// <simple-id> ::= <source-name> [ <template-args> ] func (st *state) baseUnresolvedName() AST { var n AST if len(st.str) >= 2 && st.str[:2] == "on" { @@ -2572,9 +2646,11 @@ func (st *state) baseUnresolvedName() AST { return n } -// <expr-primary> ::= L <type> <(value) number> E -// ::= L <type> <(value) float> E -// ::= L <mangled-name> E +// exprPrimary parses: +// +// <expr-primary> ::= L <type> <(value) number> E +// ::= L <type> <(value) float> E +// ::= L <mangled-name> E func (st *state) exprPrimary() AST { st.checkChar('L') if len(st.str) == 0 { @@ -2642,8 +2718,10 @@ func (st *state) exprPrimary() AST { return ret } -// <discriminator> ::= _ <(non-negative) number> (when number < 10) -// __ <(non-negative) number> _ (when number >= 10) +// discriminator parses: +// +// <discriminator> ::= _ <(non-negative) number> (when number < 10) +// __ <(non-negative) number> _ (when number >= 10) func (st *state) discriminator(a AST) AST { if len(st.str) == 0 || st.str[0] != '_' { // clang can generate a discriminator at the end of @@ -2679,8 +2757,10 @@ func (st *state) discriminator(a AST) AST { return a } -// <closure-type-name> ::= Ul <lambda-sig> E [ <nonnegative number> ] _ -// <lambda-sig> ::= <parameter type>+ +// closureTypeName parses: +// +// <closure-type-name> ::= Ul <lambda-sig> E [ <nonnegative number> ] _ +// <lambda-sig> ::= <parameter type>+ func (st *state) closureTypeName() AST { st.checkChar('U') st.checkChar('l') @@ -2721,10 +2801,12 @@ func (st *state) closureTypeName() AST { return &Closure{TemplateArgs: templateArgs, Types: types, Num: num} } -// <template-param-decl> ::= Ty # type parameter -// ::= Tn <type> # non-type parameter -// ::= Tt <template-param-decl>* E # template parameter -// ::= Tp <template-param-decl> # parameter pack +// templateParamDecl parses: +// +// <template-param-decl> ::= Ty # type parameter +// ::= Tn <type> # non-type parameter +// ::= Tt <template-param-decl>* E # template parameter +// ::= Tp <template-param-decl> # parameter pack // // Returns the new AST to include in the AST we are building and the // new AST to add to the list of template parameters. @@ -2807,7 +2889,9 @@ func (st *state) templateParamDecl() (AST, AST) { } } -// <unnamed-type-name> ::= Ut [ <nonnegative number> ] _ +// unnamedTypeName parses: +// +// <unnamed-type-name> ::= Ut [ <nonnegative number> ] _ func (st *state) unnamedTypeName() AST { st.checkChar('U') st.checkChar('t') @@ -2821,9 +2905,9 @@ func (st *state) unnamedTypeName() AST { // but are added by GCC when cloning functions. func (st *state) cloneSuffix(a AST) AST { i := 0 - if len(st.str) > 1 && st.str[0] == '.' && (isLower(st.str[1]) || st.str[1] == '_') { + if len(st.str) > 1 && st.str[0] == '.' && (isLower(st.str[1]) || isDigit(st.str[1]) || st.str[1] == '_') { i += 2 - for len(st.str) > i && (isLower(st.str[i]) || st.str[i] == '_') { + for len(st.str) > i && (isLower(st.str[i]) || isDigit(st.str[i]) || st.str[i] == '_') { i++ } } @@ -2903,15 +2987,17 @@ var verboseAST = map[byte]AST{ Args: []AST{&BuiltinType{Name: "char"}}}}}, } -// <substitution> ::= S <seq-id> _ -// ::= S_ -// ::= St -// ::= Sa -// ::= Sb -// ::= Ss -// ::= Si -// ::= So -// ::= Sd +// substitution parses: +// +// <substitution> ::= S <seq-id> _ +// ::= S_ +// ::= St +// ::= Sa +// ::= Sb +// ::= Ss +// ::= Si +// ::= So +// ::= Sd func (st *state) substitution(forPrefix bool) AST { st.checkChar('S') if len(st.str) == 0 { diff --git a/src/cmd/vendor/github.com/ianlancetaylor/demangle/rust.go b/src/cmd/vendor/github.com/ianlancetaylor/demangle/rust.go index 140b631644..39792189ac 100644 --- a/src/cmd/vendor/github.com/ianlancetaylor/demangle/rust.go +++ b/src/cmd/vendor/github.com/ianlancetaylor/demangle/rust.go @@ -47,10 +47,19 @@ func rustToString(name string, options []Option) (ret string, err error) { } if suffix != "" { - rst.skip = false - rst.writeString(" (") - rst.writeString(suffix) - rst.writeByte(')') + llvmStyle := false + for _, o := range options { + if o == LLVMStyle { + llvmStyle = true + break + } + } + if llvmStyle { + rst.skip = false + rst.writeString(" (") + rst.writeString(suffix) + rst.writeByte(')') + } } return rst.buf.String(), nil @@ -110,8 +119,10 @@ func (rst *rustState) writeString(s string) { } } -// <symbol-name> = "_R" [<decimal-number>] <path> [<instantiating-crate>] -// <instantiating-crate> = <path> +// symbolName parses: +// +// <symbol-name> = "_R" [<decimal-number>] <path> [<instantiating-crate>] +// <instantiating-crate> = <path> // // We've already skipped the "_R". func (rst *rustState) symbolName() { @@ -131,17 +142,19 @@ func (rst *rustState) symbolName() { } } -// <path> = "C" <identifier> // crate root -// | "M" <impl-path> <type> // <T> (inherent impl) -// | "X" <impl-path> <type> <path> // <T as Trait> (trait impl) -// | "Y" <type> <path> // <T as Trait> (trait definition) -// | "N" <namespace> <path> <identifier> // ...::ident (nested path) -// | "I" <path> {<generic-arg>} "E" // ...<T, U> (generic args) -// | <backref> -// <namespace> = "C" // closure -// | "S" // shim -// | <A-Z> // other special namespaces -// | <a-z> // internal namespaces +// path parses: +// +// <path> = "C" <identifier> // crate root +// | "M" <impl-path> <type> // <T> (inherent impl) +// | "X" <impl-path> <type> <path> // <T as Trait> (trait impl) +// | "Y" <type> <path> // <T as Trait> (trait definition) +// | "N" <namespace> <path> <identifier> // ...::ident (nested path) +// | "I" <path> {<generic-arg>} "E" // ...<T, U> (generic args) +// | <backref> +// <namespace> = "C" // closure +// | "S" // shim +// | <A-Z> // other special namespaces +// | <a-z> // internal namespaces // // needsSeparator is true if we need to write out :: for a generic; // it is passed as false if we are in the middle of a type. @@ -237,7 +250,9 @@ func (rst *rustState) path(needsSeparator bool) { } } -// <impl-path> = [<disambiguator>] <path> +// implPath parses: +// +// <impl-path> = [<disambiguator>] <path> func (rst *rustState) implPath() { // This path is not part of the demangled string. hold := rst.skip @@ -250,16 +265,20 @@ func (rst *rustState) implPath() { rst.path(false) } -// <identifier> = [<disambiguator>] <undisambiguated-identifier> -// Returns the disambiguator and the identifier. +// identifier parses: +// +// <identifier> = [<disambiguator>] <undisambiguated-identifier> +// +// It returns the disambiguator and the identifier. func (rst *rustState) identifier() (int64, string) { dis := rst.disambiguator() - ident := rst.undisambiguatedIdentifier() + ident, _ := rst.undisambiguatedIdentifier() return dis, ident } -// <disambiguator> = "s" <base-62-number> -// This is optional. +// disambiguator parses an optional: +// +// <disambiguator> = "s" <base-62-number> func (rst *rustState) disambiguator() int64 { if len(rst.str) == 0 || rst.str[0] != 's' { return 0 @@ -268,12 +287,14 @@ func (rst *rustState) disambiguator() int64 { return rst.base62Number() + 1 } -// <undisambiguated-identifier> = ["u"] <decimal-number> ["_"] <bytes> -func (rst *rustState) undisambiguatedIdentifier() string { - punycode := false +// undisambiguatedIdentifier parses: +// +// <undisambiguated-identifier> = ["u"] <decimal-number> ["_"] <bytes> +func (rst *rustState) undisambiguatedIdentifier() (id string, isPunycode bool) { + isPunycode = false if len(rst.str) > 0 && rst.str[0] == 'u' { rst.advance(1) - punycode = true + isPunycode = true } val := rst.decimalNumber() @@ -285,7 +306,7 @@ func (rst *rustState) undisambiguatedIdentifier() string { if len(rst.str) < val { rst.fail("not enough characters for identifier") } - id := rst.str[:val] + id = rst.str[:val] rst.advance(val) for i := 0; i < len(id); i++ { @@ -300,11 +321,11 @@ func (rst *rustState) undisambiguatedIdentifier() string { } } - if punycode { + if isPunycode { id = rst.expandPunycode(id) } - return id + return id, isPunycode } // expandPunycode decodes the Rust version of punycode. @@ -320,14 +341,18 @@ func (rst *rustState) expandPunycode(s string) string { initialN = 128 ) + var ( + output []rune + encoding string + ) idx := strings.LastIndex(s, "_") - if idx < 0 { - rst.fail("missing underscore in punycode string") + if idx >= 0 { + output = []rune(s[:idx]) + encoding = s[idx+1:] + } else { + encoding = s } - output := []rune(s[:idx]) - encoding := s[idx+1:] - i := 0 n := initialN bias := initialBias @@ -398,6 +423,8 @@ func (rst *rustState) expandPunycode(s string) string { n += i / (len(output) + 1) if n > utf8.MaxRune { rst.fail("punycode rune overflow") + } else if !utf8.ValidRune(rune(n)) { + rst.fail("punycode invalid code point") } i %= len(output) + 1 output = append(output, 0) @@ -409,10 +436,12 @@ func (rst *rustState) expandPunycode(s string) string { return string(output) } -// <generic-arg> = <lifetime> -// | <type> -// | "K" <const> // forward-compat for const generics -// <lifetime> = "L" <base-62-number> +// genericArg parses: +// +// <generic-arg> = <lifetime> +// | <type> +// | "K" <const> // forward-compat for const generics +// <lifetime> = "L" <base-62-number> func (rst *rustState) genericArg() { if len(rst.str) < 1 { rst.fail("expected generic-arg") @@ -428,8 +457,9 @@ func (rst *rustState) genericArg() { } } -// <binder> = "G" <base-62-number> -// This is optional. +// binder parses an optional: +// +// <binder> = "G" <base-62-number> func (rst *rustState) binder() { if len(rst.str) < 1 || rst.str[0] != 'G' { return @@ -454,18 +484,20 @@ func (rst *rustState) binder() { rst.writeString("> ") } -// <type> = <basic-type> -// | <path> // named type -// | "A" <type> <const> // [T; N] -// | "S" <type> // [T] -// | "T" {<type>} "E" // (T1, T2, T3, ...) -// | "R" [<lifetime>] <type> // &T -// | "Q" [<lifetime>] <type> // &mut T -// | "P" <type> // *const T -// | "O" <type> // *mut T -// | "F" <fn-sig> // fn(...) -> ... -// | "D" <dyn-bounds> <lifetime> // dyn Trait<Assoc = X> + Send + 'a -// | <backref> +// demangleType parses: +// +// <type> = <basic-type> +// | <path> // named type +// | "A" <type> <const> // [T; N] +// | "S" <type> // [T] +// | "T" {<type>} "E" // (T1, T2, T3, ...) +// | "R" [<lifetime>] <type> // &T +// | "Q" [<lifetime>] <type> // &mut T +// | "P" <type> // *const T +// | "O" <type> // *mut T +// | "F" <fn-sig> // fn(...) -> ... +// | "D" <dyn-bounds> <lifetime> // dyn Trait<Assoc = X> + Send + 'a +// | <backref> func (rst *rustState) demangleType() { if len(rst.str) < 1 { rst.fail("expected type") @@ -577,7 +609,9 @@ var rustBasicTypes = map[byte]string{ 'z': "!", } -// <basic-type> +// basicType parses: +// +// <basic-type> func (rst *rustState) basicType() { if len(rst.str) < 1 { rst.fail("expected basic type") @@ -590,9 +624,11 @@ func (rst *rustState) basicType() { rst.writeString(str) } -// <fn-sig> = [<binder>] ["U"] ["K" <abi>] {<type>} "E" <type> -// <abi> = "C" -// | <undisambiguated-identifier> +// fnSig parses: +// +// <fn-sig> = [<binder>] ["U"] ["K" <abi>] {<type>} "E" <type> +// <abi> = "C" +// | <undisambiguated-identifier> func (rst *rustState) fnSig() { rst.binder() if len(rst.str) > 0 && rst.str[0] == 'U' { @@ -606,7 +642,10 @@ func (rst *rustState) fnSig() { rst.writeString(`extern "C" `) } else { rst.writeString(`extern "`) - id := rst.undisambiguatedIdentifier() + id, isPunycode := rst.undisambiguatedIdentifier() + if isPunycode { + rst.fail("punycode used in ABI string") + } id = strings.ReplaceAll(id, "_", "-") rst.writeString(id) rst.writeString(`" `) @@ -632,7 +671,9 @@ func (rst *rustState) fnSig() { } } -// <dyn-bounds> = [<binder>] {<dyn-trait>} "E" +// dynBounds parses: +// +// <dyn-bounds> = [<binder>] {<dyn-trait>} "E" func (rst *rustState) dynBounds() { rst.writeString("dyn ") rst.binder() @@ -648,8 +689,10 @@ func (rst *rustState) dynBounds() { rst.checkChar('E') } -// <dyn-trait> = <path> {<dyn-trait-assoc-binding>} -// <dyn-trait-assoc-binding> = "p" <undisambiguated-identifier> <type> +// dynTrait parses: +// +// <dyn-trait> = <path> {<dyn-trait-assoc-binding>} +// <dyn-trait-assoc-binding> = "p" <undisambiguated-identifier> <type> func (rst *rustState) dynTrait() { started := rst.pathStartGenerics() for len(rst.str) > 0 && rst.str[0] == 'p' { @@ -660,7 +703,8 @@ func (rst *rustState) dynTrait() { rst.writeByte('<') started = true } - rst.writeString(rst.undisambiguatedIdentifier()) + id, _ := rst.undisambiguatedIdentifier() + rst.writeString(id) rst.writeString(" = ") rst.demangleType() } @@ -722,10 +766,12 @@ func (rst *rustState) writeLifetime(lifetime int64) { } } -// <const> = <type> <const-data> -// | "p" // placeholder, shown as _ -// | <backref> -// <const-data> = ["n"] {<hex-digit>} "_" +// demangleConst parses: +// +// <const> = <type> <const-data> +// | "p" // placeholder, shown as _ +// | <backref> +// <const-data> = ["n"] {<hex-digit>} "_" func (rst *rustState) demangleConst() { if len(rst.str) < 1 { rst.fail("expected constant") @@ -856,7 +902,9 @@ digitLoop: } } -// <base-62-number> = {<0-9a-zA-Z>} "_" +// base62Number parses: +// +// <base-62-number> = {<0-9a-zA-Z>} "_" func (rst *rustState) base62Number() int64 { if len(rst.str) > 0 && rst.str[0] == '_' { rst.advance(1) @@ -884,7 +932,9 @@ func (rst *rustState) base62Number() int64 { return 0 } -// <backref> = "B" <base-62-number> +// backref parses: +// +// <backref> = "B" <base-62-number> func (rst *rustState) backref(demangle func()) { backoff := rst.off @@ -954,7 +1004,7 @@ func oldRustToString(name string, options []Option) (string, bool) { // followed by "E". We check that the 16 characters are all hex digits. // Also the hex digits must contain at least 5 distinct digits. seen := uint16(0) - for i := len(name) - 17; i < len(name) - 1; i++ { + for i := len(name) - 17; i < len(name)-1; i++ { digit, ok := hexDigit(name[i]) if !ok { return "", false @@ -1012,7 +1062,7 @@ func oldRustToString(name string, options []Option) (string, bool) { for len(id) > 0 { switch c := id[0]; c { case '$': - codes := map[string]byte { + codes := map[string]byte{ "SP": '@', "BP": '*', "RF": '&', diff --git a/src/cmd/vendor/modules.txt b/src/cmd/vendor/modules.txt index 62da0c76bb..1280a9e701 100644 --- a/src/cmd/vendor/modules.txt +++ b/src/cmd/vendor/modules.txt @@ -1,5 +1,5 @@ -# github.com/google/pprof v0.0.0-20220314021825-5bba342933ea -## explicit; go 1.14 +# github.com/google/pprof v0.0.0-20220517023622-154dc81eb7b0 +## explicit; go 1.17 github.com/google/pprof/driver github.com/google/pprof/internal/binutils github.com/google/pprof/internal/driver @@ -14,7 +14,7 @@ github.com/google/pprof/internal/transport github.com/google/pprof/profile github.com/google/pprof/third_party/d3flamegraph github.com/google/pprof/third_party/svgpan -# github.com/ianlancetaylor/demangle v0.0.0-20210905161508-09a460cdf81d +# github.com/ianlancetaylor/demangle v0.0.0-20220319035150-800ac71e25c2 ## explicit; go 1.12 github.com/ianlancetaylor/demangle # golang.org/x/arch v0.0.0-20220412001346-fc48f9fe4c15 |
