",options:{disabled:!1,create:null},_createWidget:function(t,s){s=e(s||this.defaultElement||this)[0],this.element=e(s),this.uuid=i++,this.eventNamespace="."+this.widgetName+this.uuid,this.options=e.widget.extend({},this.options,this._getCreateOptions(),t),this.bindings=e(),this.hoverable=e(),this.focusable=e(),s!==this&&(e.data(s,this.widgetFullName,this),this._on(!0,this.element,{remove:function(e){e.target===s&&this.destroy()}}),this.document=e(s.style?s.ownerDocument:s.document||s),this.window=e(this.document[0].defaultView||this.document[0].parentWindow)),this._create(),this._trigger("create",null,this._getCreateEventData()),this._init()},_getCreateOptions:e.noop,_getCreateEventData:e.noop,_create:e.noop,_init:e.noop,destroy:function(){this._destroy(),this.element.unbind(this.eventNamespace).removeData(this.widgetName).removeData(this.widgetFullName).removeData(e.camelCase(this.widgetFullName)),this.widget().unbind(this.eventNamespace).removeAttr("aria-disabled").removeClass(this.widgetFullName+"-disabled "+"ui-state-disabled"),this.bindings.unbind(this.eventNamespace),this.hoverable.removeClass("ui-state-hover"),this.focusable.removeClass("ui-state-focus")},_destroy:e.noop,widget:function(){return this.element},option:function(i,s){var n,a,r,o=i;if(0===arguments.length)return e.widget.extend({},this.options);if("string"==typeof i)if(o={},n=i.split("."),i=n.shift(),n.length){for(a=o[i]=e.widget.extend({},this.options[i]),r=0;n.length-1>r;r++)a[n[r]]=a[n[r]]||{},a=a[n[r]];if(i=n.pop(),s===t)return a[i]===t?null:a[i];a[i]=s}else{if(s===t)return this.options[i]===t?null:this.options[i];o[i]=s}return this._setOptions(o),this},_setOptions:function(e){var t;for(t in e)this._setOption(t,e[t]);return this},_setOption:function(e,t){return this.options[e]=t,"disabled"===e&&(this.widget().toggleClass(this.widgetFullName+"-disabled ui-state-disabled",!!t).attr("aria-disabled",t),this.hoverable.removeClass("ui-state-hover"),this.focusable.removeClass("ui-state-focus")),this},enable:function(){return this._setOption("disabled",!1)},disable:function(){return this._setOption("disabled",!0)},_on:function(i,s,n){var a,r=this;"boolean"!=typeof i&&(n=s,s=i,i=!1),n?(s=a=e(s),this.bindings=this.bindings.add(s)):(n=s,s=this.element,a=this.widget()),e.each(n,function(n,o){function h(){return i||r.options.disabled!==!0&&!e(this).hasClass("ui-state-disabled")?("string"==typeof o?r[o]:o).apply(r,arguments):t}"string"!=typeof o&&(h.guid=o.guid=o.guid||h.guid||e.guid++);var l=n.match(/^(\w+)\s*(.*)$/),u=l[1]+r.eventNamespace,c=l[2];c?a.delegate(c,u,h):s.bind(u,h)})},_off:function(e,t){t=(t||"").split(" ").join(this.eventNamespace+" ")+this.eventNamespace,e.unbind(t).undelegate(t)},_delay:function(e,t){function i(){return("string"==typeof e?s[e]:e).apply(s,arguments)}var s=this;return setTimeout(i,t||0)},_hoverable:function(t){this.hoverable=this.hoverable.add(t),this._on(t,{mouseenter:function(t){e(t.currentTarget).addClass("ui-state-hover")},mouseleave:function(t){e(t.currentTarget).removeClass("ui-state-hover")}})},_focusable:function(t){this.focusable=this.focusable.add(t),this._on(t,{focusin:function(t){e(t.currentTarget).addClass("ui-state-focus")},focusout:function(t){e(t.currentTarget).removeClass("ui-state-focus")}})},_trigger:function(t,i,s){var n,a,r=this.options[t];if(s=s||{},i=e.Event(i),i.type=(t===this.widgetEventPrefix?t:this.widgetEventPrefix+t).toLowerCase(),i.target=this.element[0],a=i.originalEvent)for(n in a)n in i||(i[n]=a[n]);return this.element.trigger(i,s),!(e.isFunction(r)&&r.apply(this.element[0],[i].concat(s))===!1||i.isDefaultPrevented())}},e.each({show:"fadeIn",hide:"fadeOut"},function(t,i){e.Widget.prototype["_"+t]=function(s,n,a){"string"==typeof n&&(n={effect:n});var r,o=n?n===!0||"number"==typeof n?i:n.effect||i:t;n=n||{},"number"==typeof n&&(n={duration:n}),r=!e.isEmptyObject(n),n.complete=a,n.delay&&s.delay(n.delay),r&&e.effects&&e.effects.effect[o]?s[t](n):o!==t&&s[o]?s[o](n.duration,n.easing,a):s.queue(function(i){e(this)[t](),a&&a.call(s[0]),i()})}})})(jQuery);(function(e){var t=!1;e(document).mouseup(function(){t=!1}),e.widget("ui.mouse",{version:"1.10.2",options:{cancel:"input,textarea,button,select,option",distance:1,delay:0},_mouseInit:function(){var t=this;this.element.bind("mousedown."+this.widgetName,function(e){return t._mouseDown(e)}).bind("click."+this.widgetName,function(i){return!0===e.data(i.target,t.widgetName+".preventClickEvent")?(e.removeData(i.target,t.widgetName+".preventClickEvent"),i.stopImmediatePropagation(),!1):undefined}),this.started=!1},_mouseDestroy:function(){this.element.unbind("."+this.widgetName),this._mouseMoveDelegate&&e(document).unbind("mousemove."+this.widgetName,this._mouseMoveDelegate).unbind("mouseup."+this.widgetName,this._mouseUpDelegate)},_mouseDown:function(i){if(!t){this._mouseStarted&&this._mouseUp(i),this._mouseDownEvent=i;var s=this,n=1===i.which,a="string"==typeof this.options.cancel&&i.target.nodeName?e(i.target).closest(this.options.cancel).length:!1;return n&&!a&&this._mouseCapture(i)?(this.mouseDelayMet=!this.options.delay,this.mouseDelayMet||(this._mouseDelayTimer=setTimeout(function(){s.mouseDelayMet=!0},this.options.delay)),this._mouseDistanceMet(i)&&this._mouseDelayMet(i)&&(this._mouseStarted=this._mouseStart(i)!==!1,!this._mouseStarted)?(i.preventDefault(),!0):(!0===e.data(i.target,this.widgetName+".preventClickEvent")&&e.removeData(i.target,this.widgetName+".preventClickEvent"),this._mouseMoveDelegate=function(e){return s._mouseMove(e)},this._mouseUpDelegate=function(e){return s._mouseUp(e)},e(document).bind("mousemove."+this.widgetName,this._mouseMoveDelegate).bind("mouseup."+this.widgetName,this._mouseUpDelegate),i.preventDefault(),t=!0,!0)):!0}},_mouseMove:function(t){return e.ui.ie&&(!document.documentMode||9>document.documentMode)&&!t.button?this._mouseUp(t):this._mouseStarted?(this._mouseDrag(t),t.preventDefault()):(this._mouseDistanceMet(t)&&this._mouseDelayMet(t)&&(this._mouseStarted=this._mouseStart(this._mouseDownEvent,t)!==!1,this._mouseStarted?this._mouseDrag(t):this._mouseUp(t)),!this._mouseStarted)},_mouseUp:function(t){return e(document).unbind("mousemove."+this.widgetName,this._mouseMoveDelegate).unbind("mouseup."+this.widgetName,this._mouseUpDelegate),this._mouseStarted&&(this._mouseStarted=!1,t.target===this._mouseDownEvent.target&&e.data(t.target,this.widgetName+".preventClickEvent",!0),this._mouseStop(t)),!1},_mouseDistanceMet:function(e){return Math.max(Math.abs(this._mouseDownEvent.pageX-e.pageX),Math.abs(this._mouseDownEvent.pageY-e.pageY))>=this.options.distance},_mouseDelayMet:function(){return this.mouseDelayMet},_mouseStart:function(){},_mouseDrag:function(){},_mouseStop:function(){},_mouseCapture:function(){return!0}})})(jQuery);(function(e){function t(e){return parseInt(e,10)||0}function i(e){return!isNaN(parseInt(e,10))}e.widget("ui.resizable",e.ui.mouse,{version:"1.10.2",widgetEventPrefix:"resize",options:{alsoResize:!1,animate:!1,animateDuration:"slow",animateEasing:"swing",aspectRatio:!1,autoHide:!1,containment:!1,ghost:!1,grid:!1,handles:"e,s,se",helper:!1,maxHeight:null,maxWidth:null,minHeight:10,minWidth:10,zIndex:90,resize:null,start:null,stop:null},_create:function(){var t,i,s,n,a,o=this,r=this.options;if(this.element.addClass("ui-resizable"),e.extend(this,{_aspectRatio:!!r.aspectRatio,aspectRatio:r.aspectRatio,originalElement:this.element,_proportionallyResizeElements:[],_helper:r.helper||r.ghost||r.animate?r.helper||"ui-resizable-helper":null}),this.element[0].nodeName.match(/canvas|textarea|input|select|button|img/i)&&(this.element.wrap(e("
").css({position:this.element.css("position"),width:this.element.outerWidth(),height:this.element.outerHeight(),top:this.element.css("top"),left:this.element.css("left")})),this.element=this.element.parent().data("ui-resizable",this.element.data("ui-resizable")),this.elementIsWrapper=!0,this.element.css({marginLeft:this.originalElement.css("marginLeft"),marginTop:this.originalElement.css("marginTop"),marginRight:this.originalElement.css("marginRight"),marginBottom:this.originalElement.css("marginBottom")}),this.originalElement.css({marginLeft:0,marginTop:0,marginRight:0,marginBottom:0}),this.originalResizeStyle=this.originalElement.css("resize"),this.originalElement.css("resize","none"),this._proportionallyResizeElements.push(this.originalElement.css({position:"static",zoom:1,display:"block"})),this.originalElement.css({margin:this.originalElement.css("margin")}),this._proportionallyResize()),this.handles=r.handles||(e(".ui-resizable-handle",this.element).length?{n:".ui-resizable-n",e:".ui-resizable-e",s:".ui-resizable-s",w:".ui-resizable-w",se:".ui-resizable-se",sw:".ui-resizable-sw",ne:".ui-resizable-ne",nw:".ui-resizable-nw"}:"e,s,se"),this.handles.constructor===String)for("all"===this.handles&&(this.handles="n,e,s,w,se,sw,ne,nw"),t=this.handles.split(","),this.handles={},i=0;t.length>i;i++)s=e.trim(t[i]),a="ui-resizable-"+s,n=e("
"),n.css({zIndex:r.zIndex}),"se"===s&&n.addClass("ui-icon ui-icon-gripsmall-diagonal-se"),this.handles[s]=".ui-resizable-"+s,this.element.append(n);this._renderAxis=function(t){var i,s,n,a;t=t||this.element;for(i in this.handles)this.handles[i].constructor===String&&(this.handles[i]=e(this.handles[i],this.element).show()),this.elementIsWrapper&&this.originalElement[0].nodeName.match(/textarea|input|select|button/i)&&(s=e(this.handles[i],this.element),a=/sw|ne|nw|se|n|s/.test(i)?s.outerHeight():s.outerWidth(),n=["padding",/ne|nw|n/.test(i)?"Top":/se|sw|s/.test(i)?"Bottom":/^e$/.test(i)?"Right":"Left"].join(""),t.css(n,a),this._proportionallyResize()),e(this.handles[i]).length},this._renderAxis(this.element),this._handles=e(".ui-resizable-handle",this.element).disableSelection(),this._handles.mouseover(function(){o.resizing||(this.className&&(n=this.className.match(/ui-resizable-(se|sw|ne|nw|n|e|s|w)/i)),o.axis=n&&n[1]?n[1]:"se")}),r.autoHide&&(this._handles.hide(),e(this.element).addClass("ui-resizable-autohide").mouseenter(function(){r.disabled||(e(this).removeClass("ui-resizable-autohide"),o._handles.show())}).mouseleave(function(){r.disabled||o.resizing||(e(this).addClass("ui-resizable-autohide"),o._handles.hide())})),this._mouseInit()},_destroy:function(){this._mouseDestroy();var t,i=function(t){e(t).removeClass("ui-resizable ui-resizable-disabled ui-resizable-resizing").removeData("resizable").removeData("ui-resizable").unbind(".resizable").find(".ui-resizable-handle").remove()};return this.elementIsWrapper&&(i(this.element),t=this.element,this.originalElement.css({position:t.css("position"),width:t.outerWidth(),height:t.outerHeight(),top:t.css("top"),left:t.css("left")}).insertAfter(t),t.remove()),this.originalElement.css("resize",this.originalResizeStyle),i(this.originalElement),this},_mouseCapture:function(t){var i,s,n=!1;for(i in this.handles)s=e(this.handles[i])[0],(s===t.target||e.contains(s,t.target))&&(n=!0);return!this.options.disabled&&n},_mouseStart:function(i){var s,n,a,o=this.options,r=this.element.position(),h=this.element;return this.resizing=!0,/absolute/.test(h.css("position"))?h.css({position:"absolute",top:h.css("top"),left:h.css("left")}):h.is(".ui-draggable")&&h.css({position:"absolute",top:r.top,left:r.left}),this._renderProxy(),s=t(this.helper.css("left")),n=t(this.helper.css("top")),o.containment&&(s+=e(o.containment).scrollLeft()||0,n+=e(o.containment).scrollTop()||0),this.offset=this.helper.offset(),this.position={left:s,top:n},this.size=this._helper?{width:h.outerWidth(),height:h.outerHeight()}:{width:h.width(),height:h.height()},this.originalSize=this._helper?{width:h.outerWidth(),height:h.outerHeight()}:{width:h.width(),height:h.height()},this.originalPosition={left:s,top:n},this.sizeDiff={width:h.outerWidth()-h.width(),height:h.outerHeight()-h.height()},this.originalMousePosition={left:i.pageX,top:i.pageY},this.aspectRatio="number"==typeof o.aspectRatio?o.aspectRatio:this.originalSize.width/this.originalSize.height||1,a=e(".ui-resizable-"+this.axis).css("cursor"),e("body").css("cursor","auto"===a?this.axis+"-resize":a),h.addClass("ui-resizable-resizing"),this._propagate("start",i),!0},_mouseDrag:function(t){var i,s=this.helper,n={},a=this.originalMousePosition,o=this.axis,r=this.position.top,h=this.position.left,l=this.size.width,u=this.size.height,c=t.pageX-a.left||0,d=t.pageY-a.top||0,p=this._change[o];return p?(i=p.apply(this,[t,c,d]),this._updateVirtualBoundaries(t.shiftKey),(this._aspectRatio||t.shiftKey)&&(i=this._updateRatio(i,t)),i=this._respectSize(i,t),this._updateCache(i),this._propagate("resize",t),this.position.top!==r&&(n.top=this.position.top+"px"),this.position.left!==h&&(n.left=this.position.left+"px"),this.size.width!==l&&(n.width=this.size.width+"px"),this.size.height!==u&&(n.height=this.size.height+"px"),s.css(n),!this._helper&&this._proportionallyResizeElements.length&&this._proportionallyResize(),e.isEmptyObject(n)||this._trigger("resize",t,this.ui()),!1):!1},_mouseStop:function(t){this.resizing=!1;var i,s,n,a,o,r,h,l=this.options,u=this;return this._helper&&(i=this._proportionallyResizeElements,s=i.length&&/textarea/i.test(i[0].nodeName),n=s&&e.ui.hasScroll(i[0],"left")?0:u.sizeDiff.height,a=s?0:u.sizeDiff.width,o={width:u.helper.width()-a,height:u.helper.height()-n},r=parseInt(u.element.css("left"),10)+(u.position.left-u.originalPosition.left)||null,h=parseInt(u.element.css("top"),10)+(u.position.top-u.originalPosition.top)||null,l.animate||this.element.css(e.extend(o,{top:h,left:r})),u.helper.height(u.size.height),u.helper.width(u.size.width),this._helper&&!l.animate&&this._proportionallyResize()),e("body").css("cursor","auto"),this.element.removeClass("ui-resizable-resizing"),this._propagate("stop",t),this._helper&&this.helper.remove(),!1},_updateVirtualBoundaries:function(e){var t,s,n,a,o,r=this.options;o={minWidth:i(r.minWidth)?r.minWidth:0,maxWidth:i(r.maxWidth)?r.maxWidth:1/0,minHeight:i(r.minHeight)?r.minHeight:0,maxHeight:i(r.maxHeight)?r.maxHeight:1/0},(this._aspectRatio||e)&&(t=o.minHeight*this.aspectRatio,n=o.minWidth/this.aspectRatio,s=o.maxHeight*this.aspectRatio,a=o.maxWidth/this.aspectRatio,t>o.minWidth&&(o.minWidth=t),n>o.minHeight&&(o.minHeight=n),o.maxWidth>s&&(o.maxWidth=s),o.maxHeight>a&&(o.maxHeight=a)),this._vBoundaries=o},_updateCache:function(e){this.offset=this.helper.offset(),i(e.left)&&(this.position.left=e.left),i(e.top)&&(this.position.top=e.top),i(e.height)&&(this.size.height=e.height),i(e.width)&&(this.size.width=e.width)},_updateRatio:function(e){var t=this.position,s=this.size,n=this.axis;return i(e.height)?e.width=e.height*this.aspectRatio:i(e.width)&&(e.height=e.width/this.aspectRatio),"sw"===n&&(e.left=t.left+(s.width-e.width),e.top=null),"nw"===n&&(e.top=t.top+(s.height-e.height),e.left=t.left+(s.width-e.width)),e},_respectSize:function(e){var t=this._vBoundaries,s=this.axis,n=i(e.width)&&t.maxWidth&&t.maxWidth
e.width,r=i(e.height)&&t.minHeight&&t.minHeight>e.height,h=this.originalPosition.left+this.originalSize.width,l=this.position.top+this.size.height,u=/sw|nw|w/.test(s),c=/nw|ne|n/.test(s);return o&&(e.width=t.minWidth),r&&(e.height=t.minHeight),n&&(e.width=t.maxWidth),a&&(e.height=t.maxHeight),o&&u&&(e.left=h-t.minWidth),n&&u&&(e.left=h-t.maxWidth),r&&c&&(e.top=l-t.minHeight),a&&c&&(e.top=l-t.maxHeight),e.width||e.height||e.left||!e.top?e.width||e.height||e.top||!e.left||(e.left=null):e.top=null,e},_proportionallyResize:function(){if(this._proportionallyResizeElements.length){var e,t,i,s,n,a=this.helper||this.element;for(e=0;this._proportionallyResizeElements.length>e;e++){if(n=this._proportionallyResizeElements[e],!this.borderDif)for(this.borderDif=[],i=[n.css("borderTopWidth"),n.css("borderRightWidth"),n.css("borderBottomWidth"),n.css("borderLeftWidth")],s=[n.css("paddingTop"),n.css("paddingRight"),n.css("paddingBottom"),n.css("paddingLeft")],t=0;i.length>t;t++)this.borderDif[t]=(parseInt(i[t],10)||0)+(parseInt(s[t],10)||0);n.css({height:a.height()-this.borderDif[0]-this.borderDif[2]||0,width:a.width()-this.borderDif[1]-this.borderDif[3]||0})}}},_renderProxy:function(){var t=this.element,i=this.options;this.elementOffset=t.offset(),this._helper?(this.helper=this.helper||e("
"),this.helper.addClass(this._helper).css({width:this.element.outerWidth()-1,height:this.element.outerHeight()-1,position:"absolute",left:this.elementOffset.left+"px",top:this.elementOffset.top+"px",zIndex:++i.zIndex}),this.helper.appendTo("body").disableSelection()):this.helper=this.element},_change:{e:function(e,t){return{width:this.originalSize.width+t}},w:function(e,t){var i=this.originalSize,s=this.originalPosition;return{left:s.left+t,width:i.width-t}},n:function(e,t,i){var s=this.originalSize,n=this.originalPosition;return{top:n.top+i,height:s.height-i}},s:function(e,t,i){return{height:this.originalSize.height+i}},se:function(t,i,s){return e.extend(this._change.s.apply(this,arguments),this._change.e.apply(this,[t,i,s]))},sw:function(t,i,s){return e.extend(this._change.s.apply(this,arguments),this._change.w.apply(this,[t,i,s]))},ne:function(t,i,s){return e.extend(this._change.n.apply(this,arguments),this._change.e.apply(this,[t,i,s]))},nw:function(t,i,s){return e.extend(this._change.n.apply(this,arguments),this._change.w.apply(this,[t,i,s]))}},_propagate:function(t,i){e.ui.plugin.call(this,t,[i,this.ui()]),"resize"!==t&&this._trigger(t,i,this.ui())},plugins:{},ui:function(){return{originalElement:this.originalElement,element:this.element,helper:this.helper,position:this.position,size:this.size,originalSize:this.originalSize,originalPosition:this.originalPosition}}}),e.ui.plugin.add("resizable","animate",{stop:function(t){var i=e(this).data("ui-resizable"),s=i.options,n=i._proportionallyResizeElements,a=n.length&&/textarea/i.test(n[0].nodeName),o=a&&e.ui.hasScroll(n[0],"left")?0:i.sizeDiff.height,r=a?0:i.sizeDiff.width,h={width:i.size.width-r,height:i.size.height-o},l=parseInt(i.element.css("left"),10)+(i.position.left-i.originalPosition.left)||null,u=parseInt(i.element.css("top"),10)+(i.position.top-i.originalPosition.top)||null;i.element.animate(e.extend(h,u&&l?{top:u,left:l}:{}),{duration:s.animateDuration,easing:s.animateEasing,step:function(){var s={width:parseInt(i.element.css("width"),10),height:parseInt(i.element.css("height"),10),top:parseInt(i.element.css("top"),10),left:parseInt(i.element.css("left"),10)};n&&n.length&&e(n[0]).css({width:s.width,height:s.height}),i._updateCache(s),i._propagate("resize",t)}})}}),e.ui.plugin.add("resizable","containment",{start:function(){var i,s,n,a,o,r,h,l=e(this).data("ui-resizable"),u=l.options,c=l.element,d=u.containment,p=d instanceof e?d.get(0):/parent/.test(d)?c.parent().get(0):d;p&&(l.containerElement=e(p),/document/.test(d)||d===document?(l.containerOffset={left:0,top:0},l.containerPosition={left:0,top:0},l.parentData={element:e(document),left:0,top:0,width:e(document).width(),height:e(document).height()||document.body.parentNode.scrollHeight}):(i=e(p),s=[],e(["Top","Right","Left","Bottom"]).each(function(e,n){s[e]=t(i.css("padding"+n))}),l.containerOffset=i.offset(),l.containerPosition=i.position(),l.containerSize={height:i.innerHeight()-s[3],width:i.innerWidth()-s[1]},n=l.containerOffset,a=l.containerSize.height,o=l.containerSize.width,r=e.ui.hasScroll(p,"left")?p.scrollWidth:o,h=e.ui.hasScroll(p)?p.scrollHeight:a,l.parentData={element:p,left:n.left,top:n.top,width:r,height:h}))},resize:function(t){var i,s,n,a,o=e(this).data("ui-resizable"),r=o.options,h=o.containerOffset,l=o.position,u=o._aspectRatio||t.shiftKey,c={top:0,left:0},d=o.containerElement;d[0]!==document&&/static/.test(d.css("position"))&&(c=h),l.left<(o._helper?h.left:0)&&(o.size.width=o.size.width+(o._helper?o.position.left-h.left:o.position.left-c.left),u&&(o.size.height=o.size.width/o.aspectRatio),o.position.left=r.helper?h.left:0),l.top<(o._helper?h.top:0)&&(o.size.height=o.size.height+(o._helper?o.position.top-h.top:o.position.top),u&&(o.size.width=o.size.height*o.aspectRatio),o.position.top=o._helper?h.top:0),o.offset.left=o.parentData.left+o.position.left,o.offset.top=o.parentData.top+o.position.top,i=Math.abs((o._helper?o.offset.left-c.left:o.offset.left-c.left)+o.sizeDiff.width),s=Math.abs((o._helper?o.offset.top-c.top:o.offset.top-h.top)+o.sizeDiff.height),n=o.containerElement.get(0)===o.element.parent().get(0),a=/relative|absolute/.test(o.containerElement.css("position")),n&&a&&(i-=o.parentData.left),i+o.size.width>=o.parentData.width&&(o.size.width=o.parentData.width-i,u&&(o.size.height=o.size.width/o.aspectRatio)),s+o.size.height>=o.parentData.height&&(o.size.height=o.parentData.height-s,u&&(o.size.width=o.size.height*o.aspectRatio))},stop:function(){var t=e(this).data("ui-resizable"),i=t.options,s=t.containerOffset,n=t.containerPosition,a=t.containerElement,o=e(t.helper),r=o.offset(),h=o.outerWidth()-t.sizeDiff.width,l=o.outerHeight()-t.sizeDiff.height;t._helper&&!i.animate&&/relative/.test(a.css("position"))&&e(this).css({left:r.left-n.left-s.left,width:h,height:l}),t._helper&&!i.animate&&/static/.test(a.css("position"))&&e(this).css({left:r.left-n.left-s.left,width:h,height:l})}}),e.ui.plugin.add("resizable","alsoResize",{start:function(){var t=e(this).data("ui-resizable"),i=t.options,s=function(t){e(t).each(function(){var t=e(this);t.data("ui-resizable-alsoresize",{width:parseInt(t.width(),10),height:parseInt(t.height(),10),left:parseInt(t.css("left"),10),top:parseInt(t.css("top"),10)})})};"object"!=typeof i.alsoResize||i.alsoResize.parentNode?s(i.alsoResize):i.alsoResize.length?(i.alsoResize=i.alsoResize[0],s(i.alsoResize)):e.each(i.alsoResize,function(e){s(e)})},resize:function(t,i){var s=e(this).data("ui-resizable"),n=s.options,a=s.originalSize,o=s.originalPosition,r={height:s.size.height-a.height||0,width:s.size.width-a.width||0,top:s.position.top-o.top||0,left:s.position.left-o.left||0},h=function(t,s){e(t).each(function(){var t=e(this),n=e(this).data("ui-resizable-alsoresize"),a={},o=s&&s.length?s:t.parents(i.originalElement[0]).length?["width","height"]:["width","height","top","left"];e.each(o,function(e,t){var i=(n[t]||0)+(r[t]||0);i&&i>=0&&(a[t]=i||null)}),t.css(a)})};"object"!=typeof n.alsoResize||n.alsoResize.nodeType?h(n.alsoResize):e.each(n.alsoResize,function(e,t){h(e,t)})},stop:function(){e(this).removeData("resizable-alsoresize")}}),e.ui.plugin.add("resizable","ghost",{start:function(){var t=e(this).data("ui-resizable"),i=t.options,s=t.size;t.ghost=t.originalElement.clone(),t.ghost.css({opacity:.25,display:"block",position:"relative",height:s.height,width:s.width,margin:0,left:0,top:0}).addClass("ui-resizable-ghost").addClass("string"==typeof i.ghost?i.ghost:""),t.ghost.appendTo(t.helper)},resize:function(){var t=e(this).data("ui-resizable");t.ghost&&t.ghost.css({position:"relative",height:t.size.height,width:t.size.width})},stop:function(){var t=e(this).data("ui-resizable");t.ghost&&t.helper&&t.helper.get(0).removeChild(t.ghost.get(0))}}),e.ui.plugin.add("resizable","grid",{resize:function(){var t=e(this).data("ui-resizable"),i=t.options,s=t.size,n=t.originalSize,a=t.originalPosition,o=t.axis,r="number"==typeof i.grid?[i.grid,i.grid]:i.grid,h=r[0]||1,l=r[1]||1,u=Math.round((s.width-n.width)/h)*h,c=Math.round((s.height-n.height)/l)*l,d=n.width+u,p=n.height+c,f=i.maxWidth&&d>i.maxWidth,m=i.maxHeight&&p>i.maxHeight,g=i.minWidth&&i.minWidth>d,v=i.minHeight&&i.minHeight>p;i.grid=r,g&&(d+=h),v&&(p+=l),f&&(d-=h),m&&(p-=l),/^(se|s|e)$/.test(o)?(t.size.width=d,t.size.height=p):/^(ne)$/.test(o)?(t.size.width=d,t.size.height=p,t.position.top=a.top-c):/^(sw)$/.test(o)?(t.size.width=d,t.size.height=p,t.position.left=a.left-u):(t.size.width=d,t.size.height=p,t.position.top=a.top-c,t.position.left=a.left-u)}})})(jQuery);
================================================
FILE: cmd/present/static/jquery.js
================================================
/*! jQuery v1.8.2 jquery.com | jquery.org/license */
(function(a,b){function G(a){var b=F[a]={};return p.each(a.split(s),function(a,c){b[c]=!0}),b}function J(a,c,d){if(d===b&&a.nodeType===1){var e="data-"+c.replace(I,"-$1").toLowerCase();d=a.getAttribute(e);if(typeof d=="string"){try{d=d==="true"?!0:d==="false"?!1:d==="null"?null:+d+""===d?+d:H.test(d)?p.parseJSON(d):d}catch(f){}p.data(a,c,d)}else d=b}return d}function K(a){var b;for(b in a){if(b==="data"&&p.isEmptyObject(a[b]))continue;if(b!=="toJSON")return!1}return!0}function ba(){return!1}function bb(){return!0}function bh(a){return!a||!a.parentNode||a.parentNode.nodeType===11}function bi(a,b){do a=a[b];while(a&&a.nodeType!==1);return a}function bj(a,b,c){b=b||0;if(p.isFunction(b))return p.grep(a,function(a,d){var e=!!b.call(a,d,a);return e===c});if(b.nodeType)return p.grep(a,function(a,d){return a===b===c});if(typeof b=="string"){var d=p.grep(a,function(a){return a.nodeType===1});if(be.test(b))return p.filter(b,d,!c);b=p.filter(b,d)}return p.grep(a,function(a,d){return p.inArray(a,b)>=0===c})}function bk(a){var b=bl.split("|"),c=a.createDocumentFragment();if(c.createElement)while(b.length)c.createElement(b.pop());return c}function bC(a,b){return a.getElementsByTagName(b)[0]||a.appendChild(a.ownerDocument.createElement(b))}function bD(a,b){if(b.nodeType!==1||!p.hasData(a))return;var c,d,e,f=p._data(a),g=p._data(b,f),h=f.events;if(h){delete g.handle,g.events={};for(c in h)for(d=0,e=h[c].length;d").appendTo(e.body),c=b.css("display");b.remove();if(c==="none"||c===""){bI=e.body.appendChild(bI||p.extend(e.createElement("iframe"),{frameBorder:0,width:0,height:0}));if(!bJ||!bI.createElement)bJ=(bI.contentWindow||bI.contentDocument).document,bJ.write(""),bJ.close();b=bJ.body.appendChild(bJ.createElement(a)),c=bH(b,"display"),e.body.removeChild(bI)}return bS[a]=c,c}function ci(a,b,c,d){var e;if(p.isArray(b))p.each(b,function(b,e){c||ce.test(a)?d(a,e):ci(a+"["+(typeof e=="object"?b:"")+"]",e,c,d)});else if(!c&&p.type(b)==="object")for(e in b)ci(a+"["+e+"]",b[e],c,d);else d(a,b)}function cz(a){return function(b,c){typeof b!="string"&&(c=b,b="*");var d,e,f,g=b.toLowerCase().split(s),h=0,i=g.length;if(p.isFunction(c))for(;h)[^>]*$|#([\w\-]*)$)/,v=/^<(\w+)\s*\/?>(?:<\/\1>|)$/,w=/^[\],:{}\s]*$/,x=/(?:^|:|,)(?:\s*\[)+/g,y=/\\(?:["\\\/bfnrt]|u[\da-fA-F]{4})/g,z=/"[^"\\\r\n]*"|true|false|null|-?(?:\d\d*\.|)\d+(?:[eE][\-+]?\d+|)/g,A=/^-ms-/,B=/-([\da-z])/gi,C=function(a,b){return(b+"").toUpperCase()},D=function(){e.addEventListener?(e.removeEventListener("DOMContentLoaded",D,!1),p.ready()):e.readyState==="complete"&&(e.detachEvent("onreadystatechange",D),p.ready())},E={};p.fn=p.prototype={constructor:p,init:function(a,c,d){var f,g,h,i;if(!a)return this;if(a.nodeType)return this.context=this[0]=a,this.length=1,this;if(typeof a=="string"){a.charAt(0)==="<"&&a.charAt(a.length-1)===">"&&a.length>=3?f=[null,a,null]:f=u.exec(a);if(f&&(f[1]||!c)){if(f[1])return c=c instanceof p?c[0]:c,i=c&&c.nodeType?c.ownerDocument||c:e,a=p.parseHTML(f[1],i,!0),v.test(f[1])&&p.isPlainObject(c)&&this.attr.call(a,c,!0),p.merge(this,a);g=e.getElementById(f[2]);if(g&&g.parentNode){if(g.id!==f[2])return d.find(a);this.length=1,this[0]=g}return this.context=e,this.selector=a,this}return!c||c.jquery?(c||d).find(a):this.constructor(c).find(a)}return p.isFunction(a)?d.ready(a):(a.selector!==b&&(this.selector=a.selector,this.context=a.context),p.makeArray(a,this))},selector:"",jquery:"1.8.2",length:0,size:function(){return this.length},toArray:function(){return k.call(this)},get:function(a){return a==null?this.toArray():a<0?this[this.length+a]:this[a]},pushStack:function(a,b,c){var d=p.merge(this.constructor(),a);return d.prevObject=this,d.context=this.context,b==="find"?d.selector=this.selector+(this.selector?" ":"")+c:b&&(d.selector=this.selector+"."+b+"("+c+")"),d},each:function(a,b){return p.each(this,a,b)},ready:function(a){return p.ready.promise().done(a),this},eq:function(a){return a=+a,a===-1?this.slice(a):this.slice(a,a+1)},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},slice:function(){return this.pushStack(k.apply(this,arguments),"slice",k.call(arguments).join(","))},map:function(a){return this.pushStack(p.map(this,function(b,c){return a.call(b,c,b)}))},end:function(){return this.prevObject||this.constructor(null)},push:j,sort:[].sort,splice:[].splice},p.fn.init.prototype=p.fn,p.extend=p.fn.extend=function(){var a,c,d,e,f,g,h=arguments[0]||{},i=1,j=arguments.length,k=!1;typeof h=="boolean"&&(k=h,h=arguments[1]||{},i=2),typeof h!="object"&&!p.isFunction(h)&&(h={}),j===i&&(h=this,--i);for(;i0)return;d.resolveWith(e,[p]),p.fn.trigger&&p(e).trigger("ready").off("ready")},isFunction:function(a){return p.type(a)==="function"},isArray:Array.isArray||function(a){return p.type(a)==="array"},isWindow:function(a){return a!=null&&a==a.window},isNumeric:function(a){return!isNaN(parseFloat(a))&&isFinite(a)},type:function(a){return a==null?String(a):E[m.call(a)]||"object"},isPlainObject:function(a){if(!a||p.type(a)!=="object"||a.nodeType||p.isWindow(a))return!1;try{if(a.constructor&&!n.call(a,"constructor")&&!n.call(a.constructor.prototype,"isPrototypeOf"))return!1}catch(c){return!1}var d;for(d in a);return d===b||n.call(a,d)},isEmptyObject:function(a){var b;for(b in a)return!1;return!0},error:function(a){throw new Error(a)},parseHTML:function(a,b,c){var d;return!a||typeof a!="string"?null:(typeof b=="boolean"&&(c=b,b=0),b=b||e,(d=v.exec(a))?[b.createElement(d[1])]:(d=p.buildFragment([a],b,c?null:[]),p.merge([],(d.cacheable?p.clone(d.fragment):d.fragment).childNodes)))},parseJSON:function(b){if(!b||typeof b!="string")return null;b=p.trim(b);if(a.JSON&&a.JSON.parse)return a.JSON.parse(b);if(w.test(b.replace(y,"@").replace(z,"]").replace(x,"")))return(new Function("return "+b))();p.error("Invalid JSON: "+b)},parseXML:function(c){var d,e;if(!c||typeof c!="string")return null;try{a.DOMParser?(e=new DOMParser,d=e.parseFromString(c,"text/xml")):(d=new ActiveXObject("Microsoft.XMLDOM"),d.async="false",d.loadXML(c))}catch(f){d=b}return(!d||!d.documentElement||d.getElementsByTagName("parsererror").length)&&p.error("Invalid XML: "+c),d},noop:function(){},globalEval:function(b){b&&r.test(b)&&(a.execScript||function(b){a.eval.call(a,b)})(b)},camelCase:function(a){return a.replace(A,"ms-").replace(B,C)},nodeName:function(a,b){return a.nodeName&&a.nodeName.toLowerCase()===b.toLowerCase()},each:function(a,c,d){var e,f=0,g=a.length,h=g===b||p.isFunction(a);if(d){if(h){for(e in a)if(c.apply(a[e],d)===!1)break}else for(;f0&&a[0]&&a[i-1]||i===0||p.isArray(a));if(j)for(;h-1)i.splice(c,1),e&&(c<=g&&g--,c<=h&&h--)}),this},has:function(a){return p.inArray(a,i)>-1},empty:function(){return i=[],this},disable:function(){return i=j=c=b,this},disabled:function(){return!i},lock:function(){return j=b,c||l.disable(),this},locked:function(){return!j},fireWith:function(a,b){return b=b||[],b=[a,b.slice?b.slice():b],i&&(!d||j)&&(e?j.push(b):k(b)),this},fire:function(){return l.fireWith(this,arguments),this},fired:function(){return!!d}};return l},p.extend({Deferred:function(a){var b=[["resolve","done",p.Callbacks("once memory"),"resolved"],["reject","fail",p.Callbacks("once memory"),"rejected"],["notify","progress",p.Callbacks("memory")]],c="pending",d={state:function(){return c},always:function(){return e.done(arguments).fail(arguments),this},then:function(){var a=arguments;return p.Deferred(function(c){p.each(b,function(b,d){var f=d[0],g=a[b];e[d[1]](p.isFunction(g)?function(){var a=g.apply(this,arguments);a&&p.isFunction(a.promise)?a.promise().done(c.resolve).fail(c.reject).progress(c.notify):c[f+"With"](this===e?c:this,[a])}:c[f])}),a=null}).promise()},promise:function(a){return a!=null?p.extend(a,d):d}},e={};return d.pipe=d.then,p.each(b,function(a,f){var g=f[2],h=f[3];d[f[1]]=g.add,h&&g.add(function(){c=h},b[a^1][2].disable,b[2][2].lock),e[f[0]]=g.fire,e[f[0]+"With"]=g.fireWith}),d.promise(e),a&&a.call(e,e),e},when:function(a){var b=0,c=k.call(arguments),d=c.length,e=d!==1||a&&p.isFunction(a.promise)?d:0,f=e===1?a:p.Deferred(),g=function(a,b,c){return function(d){b[a]=this,c[a]=arguments.length>1?k.call(arguments):d,c===h?f.notifyWith(b,c):--e||f.resolveWith(b,c)}},h,i,j;if(d>1){h=new Array(d),i=new Array(d),j=new Array(d);for(;ba ",c=n.getElementsByTagName("*"),d=n.getElementsByTagName("a")[0],d.style.cssText="top:1px;float:left;opacity:.5";if(!c||!c.length)return{};f=e.createElement("select"),g=f.appendChild(e.createElement("option")),h=n.getElementsByTagName("input")[0],b={leadingWhitespace:n.firstChild.nodeType===3,tbody:!n.getElementsByTagName("tbody").length,htmlSerialize:!!n.getElementsByTagName("link").length,style:/top/.test(d.getAttribute("style")),hrefNormalized:d.getAttribute("href")==="/a",opacity:/^0.5/.test(d.style.opacity),cssFloat:!!d.style.cssFloat,checkOn:h.value==="on",optSelected:g.selected,getSetAttribute:n.className!=="t",enctype:!!e.createElement("form").enctype,html5Clone:e.createElement("nav").cloneNode(!0).outerHTML!=="<:nav>",boxModel:e.compatMode==="CSS1Compat",submitBubbles:!0,changeBubbles:!0,focusinBubbles:!1,deleteExpando:!0,noCloneEvent:!0,inlineBlockNeedsLayout:!1,shrinkWrapBlocks:!1,reliableMarginRight:!0,boxSizingReliable:!0,pixelPosition:!1},h.checked=!0,b.noCloneChecked=h.cloneNode(!0).checked,f.disabled=!0,b.optDisabled=!g.disabled;try{delete n.test}catch(o){b.deleteExpando=!1}!n.addEventListener&&n.attachEvent&&n.fireEvent&&(n.attachEvent("onclick",m=function(){b.noCloneEvent=!1}),n.cloneNode(!0).fireEvent("onclick"),n.detachEvent("onclick",m)),h=e.createElement("input"),h.value="t",h.setAttribute("type","radio"),b.radioValue=h.value==="t",h.setAttribute("checked","checked"),h.setAttribute("name","t"),n.appendChild(h),i=e.createDocumentFragment(),i.appendChild(n.lastChild),b.checkClone=i.cloneNode(!0).cloneNode(!0).lastChild.checked,b.appendChecked=h.checked,i.removeChild(h),i.appendChild(n);if(n.attachEvent)for(k in{submit:!0,change:!0,focusin:!0})j="on"+k,l=j in n,l||(n.setAttribute(j,"return;"),l=typeof n[j]=="function"),b[k+"Bubbles"]=l;return p(function(){var c,d,f,g,h="padding:0;margin:0;border:0;display:block;overflow:hidden;",i=e.getElementsByTagName("body")[0];if(!i)return;c=e.createElement("div"),c.style.cssText="visibility:hidden;border:0;width:0;height:0;position:static;top:0;margin-top:1px",i.insertBefore(c,i.firstChild),d=e.createElement("div"),c.appendChild(d),d.innerHTML="",f=d.getElementsByTagName("td"),f[0].style.cssText="padding:0;margin:0;border:0;display:none",l=f[0].offsetHeight===0,f[0].style.display="",f[1].style.display="none",b.reliableHiddenOffsets=l&&f[0].offsetHeight===0,d.innerHTML="",d.style.cssText="box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;padding:1px;border:1px;display:block;width:4px;margin-top:1%;position:absolute;top:1%;",b.boxSizing=d.offsetWidth===4,b.doesNotIncludeMarginInBodyOffset=i.offsetTop!==1,a.getComputedStyle&&(b.pixelPosition=(a.getComputedStyle(d,null)||{}).top!=="1%",b.boxSizingReliable=(a.getComputedStyle(d,null)||{width:"4px"}).width==="4px",g=e.createElement("div"),g.style.cssText=d.style.cssText=h,g.style.marginRight=g.style.width="0",d.style.width="1px",d.appendChild(g),b.reliableMarginRight=!parseFloat((a.getComputedStyle(g,null)||{}).marginRight)),typeof d.style.zoom!="undefined"&&(d.innerHTML="",d.style.cssText=h+"width:1px;padding:1px;display:inline;zoom:1",b.inlineBlockNeedsLayout=d.offsetWidth===3,d.style.display="block",d.style.overflow="visible",d.innerHTML="
",d.firstChild.style.width="5px",b.shrinkWrapBlocks=d.offsetWidth!==3,c.style.zoom=1),i.removeChild(c),c=d=f=g=null}),i.removeChild(n),c=d=f=g=h=i=n=null,b}();var H=/(?:\{[\s\S]*\}|\[[\s\S]*\])$/,I=/([A-Z])/g;p.extend({cache:{},deletedIds:[],uuid:0,expando:"jQuery"+(p.fn.jquery+Math.random()).replace(/\D/g,""),noData:{embed:!0,object:"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000",applet:!0},hasData:function(a){return a=a.nodeType?p.cache[a[p.expando]]:a[p.expando],!!a&&!K(a)},data:function(a,c,d,e){if(!p.acceptData(a))return;var f,g,h=p.expando,i=typeof c=="string",j=a.nodeType,k=j?p.cache:a,l=j?a[h]:a[h]&&h;if((!l||!k[l]||!e&&!k[l].data)&&i&&d===b)return;l||(j?a[h]=l=p.deletedIds.pop()||p.guid++:l=h),k[l]||(k[l]={},j||(k[l].toJSON=p.noop));if(typeof c=="object"||typeof c=="function")e?k[l]=p.extend(k[l],c):k[l].data=p.extend(k[l].data,c);return f=k[l],e||(f.data||(f.data={}),f=f.data),d!==b&&(f[p.camelCase(c)]=d),i?(g=f[c],g==null&&(g=f[p.camelCase(c)])):g=f,g},removeData:function(a,b,c){if(!p.acceptData(a))return;var d,e,f,g=a.nodeType,h=g?p.cache:a,i=g?a[p.expando]:p.expando;if(!h[i])return;if(b){d=c?h[i]:h[i].data;if(d){p.isArray(b)||(b in d?b=[b]:(b=p.camelCase(b),b in d?b=[b]:b=b.split(" ")));for(e=0,f=b.length;e1,null,!1))},removeData:function(a){return this.each(function(){p.removeData(this,a)})}}),p.extend({queue:function(a,b,c){var d;if(a)return b=(b||"fx")+"queue",d=p._data(a,b),c&&(!d||p.isArray(c)?d=p._data(a,b,p.makeArray(c)):d.push(c)),d||[]},dequeue:function(a,b){b=b||"fx";var c=p.queue(a,b),d=c.length,e=c.shift(),f=p._queueHooks(a,b),g=function(){p.dequeue(a,b)};e==="inprogress"&&(e=c.shift(),d--),e&&(b==="fx"&&c.unshift("inprogress"),delete f.stop,e.call(a,g,f)),!d&&f&&f.empty.fire()},_queueHooks:function(a,b){var c=b+"queueHooks";return p._data(a,c)||p._data(a,c,{empty:p.Callbacks("once memory").add(function(){p.removeData(a,b+"queue",!0),p.removeData(a,c,!0)})})}}),p.fn.extend({queue:function(a,c){var d=2;return typeof a!="string"&&(c=a,a="fx",d--),arguments.length1)},removeAttr:function(a){return this.each(function(){p.removeAttr(this,a)})},prop:function(a,b){return p.access(this,p.prop,a,b,arguments.length>1)},removeProp:function(a){return a=p.propFix[a]||a,this.each(function(){try{this[a]=b,delete this[a]}catch(c){}})},addClass:function(a){var b,c,d,e,f,g,h;if(p.isFunction(a))return this.each(function(b){p(this).addClass(a.call(this,b,this.className))});if(a&&typeof a=="string"){b=a.split(s);for(c=0,d=this.length;c=0)d=d.replace(" "+c[f]+" "," ");e.className=a?p.trim(d):""}}}return this},toggleClass:function(a,b){var c=typeof a,d=typeof b=="boolean";return p.isFunction(a)?this.each(function(c){p(this).toggleClass(a.call(this,c,this.className,b),b)}):this.each(function(){if(c==="string"){var e,f=0,g=p(this),h=b,i=a.split(s);while(e=i[f++])h=d?h:!g.hasClass(e),g[h?"addClass":"removeClass"](e)}else if(c==="undefined"||c==="boolean")this.className&&p._data(this,"__className__",this.className),this.className=this.className||a===!1?"":p._data(this,"__className__")||""})},hasClass:function(a){var b=" "+a+" ",c=0,d=this.length;for(;c=0)return!0;return!1},val:function(a){var c,d,e,f=this[0];if(!arguments.length){if(f)return c=p.valHooks[f.type]||p.valHooks[f.nodeName.toLowerCase()],c&&"get"in c&&(d=c.get(f,"value"))!==b?d:(d=f.value,typeof d=="string"?d.replace(P,""):d==null?"":d);return}return e=p.isFunction(a),this.each(function(d){var f,g=p(this);if(this.nodeType!==1)return;e?f=a.call(this,d,g.val()):f=a,f==null?f="":typeof f=="number"?f+="":p.isArray(f)&&(f=p.map(f,function(a){return a==null?"":a+""})),c=p.valHooks[this.type]||p.valHooks[this.nodeName.toLowerCase()];if(!c||!("set"in c)||c.set(this,f,"value")===b)this.value=f})}}),p.extend({valHooks:{option:{get:function(a){var b=a.attributes.value;return!b||b.specified?a.value:a.text}},select:{get:function(a){var b,c,d,e,f=a.selectedIndex,g=[],h=a.options,i=a.type==="select-one";if(f<0)return null;c=i?f:0,d=i?f+1:h.length;for(;c=0}),c.length||(a.selectedIndex=-1),c}}},attrFn:{},attr:function(a,c,d,e){var f,g,h,i=a.nodeType;if(!a||i===3||i===8||i===2)return;if(e&&p.isFunction(p.fn[c]))return p(a)[c](d);if(typeof a.getAttribute=="undefined")return p.prop(a,c,d);h=i!==1||!p.isXMLDoc(a),h&&(c=c.toLowerCase(),g=p.attrHooks[c]||(T.test(c)?M:L));if(d!==b){if(d===null){p.removeAttr(a,c);return}return g&&"set"in g&&h&&(f=g.set(a,d,c))!==b?f:(a.setAttribute(c,d+""),d)}return g&&"get"in g&&h&&(f=g.get(a,c))!==null?f:(f=a.getAttribute(c),f===null?b:f)},removeAttr:function(a,b){var c,d,e,f,g=0;if(b&&a.nodeType===1){d=b.split(s);for(;g=0}})});var V=/^(?:textarea|input|select)$/i,W=/^([^\.]*|)(?:\.(.+)|)$/,X=/(?:^|\s)hover(\.\S+|)\b/,Y=/^key/,Z=/^(?:mouse|contextmenu)|click/,$=/^(?:focusinfocus|focusoutblur)$/,_=function(a){return p.event.special.hover?a:a.replace(X,"mouseenter$1 mouseleave$1")};p.event={add:function(a,c,d,e,f){var g,h,i,j,k,l,m,n,o,q,r;if(a.nodeType===3||a.nodeType===8||!c||!d||!(g=p._data(a)))return;d.handler&&(o=d,d=o.handler,f=o.selector),d.guid||(d.guid=p.guid++),i=g.events,i||(g.events=i={}),h=g.handle,h||(g.handle=h=function(a){return typeof p!="undefined"&&(!a||p.event.triggered!==a.type)?p.event.dispatch.apply(h.elem,arguments):b},h.elem=a),c=p.trim(_(c)).split(" ");for(j=0;j=0&&(s=s.slice(0,-1),i=!0),s.indexOf(".")>=0&&(t=s.split("."),s=t.shift(),t.sort());if((!f||p.event.customEvent[s])&&!p.event.global[s])return;c=typeof c=="object"?c[p.expando]?c:new p.Event(s,c):new p.Event(s),c.type=s,c.isTrigger=!0,c.exclusive=i,c.namespace=t.join("."),c.namespace_re=c.namespace?new RegExp("(^|\\.)"+t.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,m=s.indexOf(":")<0?"on"+s:"";if(!f){h=p.cache;for(j in h)h[j].events&&h[j].events[s]&&p.event.trigger(c,d,h[j].handle.elem,!0);return}c.result=b,c.target||(c.target=f),d=d!=null?p.makeArray(d):[],d.unshift(c),n=p.event.special[s]||{};if(n.trigger&&n.trigger.apply(f,d)===!1)return;q=[[f,n.bindType||s]];if(!g&&!n.noBubble&&!p.isWindow(f)){r=n.delegateType||s,k=$.test(r+s)?f:f.parentNode;for(l=f;k;k=k.parentNode)q.push([k,r]),l=k;l===(f.ownerDocument||e)&&q.push([l.defaultView||l.parentWindow||a,r])}for(j=0;j=0:p.find(m,this,null,[f]).length),h[m]&&j.push(l);j.length&&u.push({elem:f,matches:j})}o.length>q&&u.push({elem:this,matches:o.slice(q)});for(d=0;d0?this.on(b,null,a,c):this.trigger(b)},Y.test(b)&&(p.event.fixHooks[b]=p.event.keyHooks),Z.test(b)&&(p.event.fixHooks[b]=p.event.mouseHooks)}),function(a,b){function bc(a,b,c,d){c=c||[],b=b||r;var e,f,i,j,k=b.nodeType;if(!a||typeof a!="string")return c;if(k!==1&&k!==9)return[];i=g(b);if(!i&&!d)if(e=P.exec(a))if(j=e[1]){if(k===9){f=b.getElementById(j);if(!f||!f.parentNode)return c;if(f.id===j)return c.push(f),c}else if(b.ownerDocument&&(f=b.ownerDocument.getElementById(j))&&h(b,f)&&f.id===j)return c.push(f),c}else{if(e[2])return w.apply(c,x.call(b.getElementsByTagName(a),0)),c;if((j=e[3])&&_&&b.getElementsByClassName)return w.apply(c,x.call(b.getElementsByClassName(j),0)),c}return bp(a.replace(L,"$1"),b,c,d,i)}function bd(a){return function(b){var c=b.nodeName.toLowerCase();return c==="input"&&b.type===a}}function be(a){return function(b){var c=b.nodeName.toLowerCase();return(c==="input"||c==="button")&&b.type===a}}function bf(a){return z(function(b){return b=+b,z(function(c,d){var e,f=a([],c.length,b),g=f.length;while(g--)c[e=f[g]]&&(c[e]=!(d[e]=c[e]))})})}function bg(a,b,c){if(a===b)return c;var d=a.nextSibling;while(d){if(d===b)return-1;d=d.nextSibling}return 1}function bh(a,b){var c,d,f,g,h,i,j,k=C[o][a];if(k)return b?0:k.slice(0);h=a,i=[],j=e.preFilter;while(h){if(!c||(d=M.exec(h)))d&&(h=h.slice(d[0].length)),i.push(f=[]);c=!1;if(d=N.exec(h))f.push(c=new q(d.shift())),h=h.slice(c.length),c.type=d[0].replace(L," ");for(g in e.filter)(d=W[g].exec(h))&&(!j[g]||(d=j[g](d,r,!0)))&&(f.push(c=new q(d.shift())),h=h.slice(c.length),c.type=g,c.matches=d);if(!c)break}return b?h.length:h?bc.error(a):C(a,i).slice(0)}function bi(a,b,d){var e=b.dir,f=d&&b.dir==="parentNode",g=u++;return b.first?function(b,c,d){while(b=b[e])if(f||b.nodeType===1)return a(b,c,d)}:function(b,d,h){if(!h){var i,j=t+" "+g+" ",k=j+c;while(b=b[e])if(f||b.nodeType===1){if((i=b[o])===k)return b.sizset;if(typeof i=="string"&&i.indexOf(j)===0){if(b.sizset)return b}else{b[o]=k;if(a(b,d,h))return b.sizset=!0,b;b.sizset=!1}}}else while(b=b[e])if(f||b.nodeType===1)if(a(b,d,h))return b}}function bj(a){return a.length>1?function(b,c,d){var e=a.length;while(e--)if(!a[e](b,c,d))return!1;return!0}:a[0]}function bk(a,b,c,d,e){var f,g=[],h=0,i=a.length,j=b!=null;for(;h-1},h,!0),m=[function(a,c,d){return!g&&(d||c!==l)||((b=c).nodeType?j(a,c,d):k(a,c,d))}];for(;i1&&bj(m),i>1&&a.slice(0,i-1).join("").replace(L,"$1"),c,i0,f=a.length>0,g=function(h,i,j,k,m){var n,o,p,q=[],s=0,u="0",x=h&&[],y=m!=null,z=l,A=h||f&&e.find.TAG("*",m&&i.parentNode||i),B=t+=z==null?1:Math.E;y&&(l=i!==r&&i,c=g.el);for(;(n=A[u])!=null;u++){if(f&&n){for(o=0;p=a[o];o++)if(p(n,i,j)){k.push(n);break}y&&(t=B,c=++g.el)}d&&((n=!p&&n)&&s--,h&&x.push(n))}s+=u;if(d&&u!==s){for(o=0;p=b[o];o++)p(x,q,i,j);if(h){if(s>0)while(u--)!x[u]&&!q[u]&&(q[u]=v.call(k));q=bk(q)}w.apply(k,q),y&&!h&&q.length>0&&s+b.length>1&&bc.uniqueSort(k)}return y&&(t=B,l=z),x};return g.el=0,d?z(g):g}function bo(a,b,c,d){var e=0,f=b.length;for(;e2&&(j=h[0]).type==="ID"&&b.nodeType===9&&!f&&e.relative[h[1].type]){b=e.find.ID(j.matches[0].replace(V,""),b,f)[0];if(!b)return c;a=a.slice(h.shift().length)}for(g=W.POS.test(a)?-1:h.length-1;g>=0;g--){j=h[g];if(e.relative[k=j.type])break;if(l=e.find[k])if(d=l(j.matches[0].replace(V,""),R.test(h[0].type)&&b.parentNode||b,f)){h.splice(g,1),a=d.length&&h.join("");if(!a)return w.apply(c,x.call(d,0)),c;break}}}return i(a,m)(d,b,f,c,R.test(a)),c}function bq(){}var c,d,e,f,g,h,i,j,k,l,m=!0,n="undefined",o=("sizcache"+Math.random()).replace(".",""),q=String,r=a.document,s=r.documentElement,t=0,u=0,v=[].pop,w=[].push,x=[].slice,y=[].indexOf||function(a){var b=0,c=this.length;for(;be.cacheLength&&delete a[b.shift()],a[c]=d},a)},B=A(),C=A(),D=A(),E="[\\x20\\t\\r\\n\\f]",F="(?:\\\\.|[-\\w]|[^\\x00-\\xa0])+",G=F.replace("w","w#"),H="([*^$|!~]?=)",I="\\["+E+"*("+F+")"+E+"*(?:"+H+E+"*(?:(['\"])((?:\\\\.|[^\\\\])*?)\\3|("+G+")|)|)"+E+"*\\]",J=":("+F+")(?:\\((?:(['\"])((?:\\\\.|[^\\\\])*?)\\2|([^()[\\]]*|(?:(?:"+I+")|[^:]|\\\\.)*|.*))\\)|)",K=":(even|odd|eq|gt|lt|nth|first|last)(?:\\("+E+"*((?:-\\d)?\\d*)"+E+"*\\)|)(?=[^-]|$)",L=new RegExp("^"+E+"+|((?:^|[^\\\\])(?:\\\\.)*)"+E+"+$","g"),M=new RegExp("^"+E+"*,"+E+"*"),N=new RegExp("^"+E+"*([\\x20\\t\\r\\n\\f>+~])"+E+"*"),O=new RegExp(J),P=/^(?:#([\w\-]+)|(\w+)|\.([\w\-]+))$/,Q=/^:not/,R=/[\x20\t\r\n\f]*[+~]/,S=/:not\($/,T=/h\d/i,U=/input|select|textarea|button/i,V=/\\(?!\\)/g,W={ID:new RegExp("^#("+F+")"),CLASS:new RegExp("^\\.("+F+")"),NAME:new RegExp("^\\[name=['\"]?("+F+")['\"]?\\]"),TAG:new RegExp("^("+F.replace("w","w*")+")"),ATTR:new RegExp("^"+I),PSEUDO:new RegExp("^"+J),POS:new RegExp(K,"i"),CHILD:new RegExp("^:(only|nth|first|last)-child(?:\\("+E+"*(even|odd|(([+-]|)(\\d*)n|)"+E+"*(?:([+-]|)"+E+"*(\\d+)|))"+E+"*\\)|)","i"),needsContext:new RegExp("^"+E+"*[>+~]|"+K,"i")},X=function(a){var b=r.createElement("div");try{return a(b)}catch(c){return!1}finally{b=null}},Y=X(function(a){return a.appendChild(r.createComment("")),!a.getElementsByTagName("*").length}),Z=X(function(a){return a.innerHTML=" ",a.firstChild&&typeof a.firstChild.getAttribute!==n&&a.firstChild.getAttribute("href")==="#"}),$=X(function(a){a.innerHTML=" ";var b=typeof a.lastChild.getAttribute("multiple");return b!=="boolean"&&b!=="string"}),_=X(function(a){return a.innerHTML="
",!a.getElementsByClassName||!a.getElementsByClassName("e").length?!1:(a.lastChild.className="e",a.getElementsByClassName("e").length===2)}),ba=X(function(a){a.id=o+0,a.innerHTML="
",s.insertBefore(a,s.firstChild);var b=r.getElementsByName&&r.getElementsByName(o).length===2+r.getElementsByName(o+0).length;return d=!r.getElementById(o),s.removeChild(a),b});try{x.call(s.childNodes,0)[0].nodeType}catch(bb){x=function(a){var b,c=[];for(;b=this[a];a++)c.push(b);return c}}bc.matches=function(a,b){return bc(a,null,null,b)},bc.matchesSelector=function(a,b){return bc(b,null,null,[a]).length>0},f=bc.getText=function(a){var b,c="",d=0,e=a.nodeType;if(e){if(e===1||e===9||e===11){if(typeof a.textContent=="string")return a.textContent;for(a=a.firstChild;a;a=a.nextSibling)c+=f(a)}else if(e===3||e===4)return a.nodeValue}else for(;b=a[d];d++)c+=f(b);return c},g=bc.isXML=function(a){var b=a&&(a.ownerDocument||a).documentElement;return b?b.nodeName!=="HTML":!1},h=bc.contains=s.contains?function(a,b){var c=a.nodeType===9?a.documentElement:a,d=b&&b.parentNode;return a===d||!!(d&&d.nodeType===1&&c.contains&&c.contains(d))}:s.compareDocumentPosition?function(a,b){return b&&!!(a.compareDocumentPosition(b)&16)}:function(a,b){while(b=b.parentNode)if(b===a)return!0;return!1},bc.attr=function(a,b){var c,d=g(a);return d||(b=b.toLowerCase()),(c=e.attrHandle[b])?c(a):d||$?a.getAttribute(b):(c=a.getAttributeNode(b),c?typeof a[b]=="boolean"?a[b]?b:null:c.specified?c.value:null:null)},e=bc.selectors={cacheLength:50,createPseudo:z,match:W,attrHandle:Z?{}:{href:function(a){return a.getAttribute("href",2)},type:function(a){return a.getAttribute("type")}},find:{ID:d?function(a,b,c){if(typeof b.getElementById!==n&&!c){var d=b.getElementById(a);return d&&d.parentNode?[d]:[]}}:function(a,c,d){if(typeof c.getElementById!==n&&!d){var e=c.getElementById(a);return e?e.id===a||typeof e.getAttributeNode!==n&&e.getAttributeNode("id").value===a?[e]:b:[]}},TAG:Y?function(a,b){if(typeof b.getElementsByTagName!==n)return b.getElementsByTagName(a)}:function(a,b){var c=b.getElementsByTagName(a);if(a==="*"){var d,e=[],f=0;for(;d=c[f];f++)d.nodeType===1&&e.push(d);return e}return c},NAME:ba&&function(a,b){if(typeof b.getElementsByName!==n)return b.getElementsByName(name)},CLASS:_&&function(a,b,c){if(typeof b.getElementsByClassName!==n&&!c)return b.getElementsByClassName(a)}},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(a){return a[1]=a[1].replace(V,""),a[3]=(a[4]||a[5]||"").replace(V,""),a[2]==="~="&&(a[3]=" "+a[3]+" "),a.slice(0,4)},CHILD:function(a){return a[1]=a[1].toLowerCase(),a[1]==="nth"?(a[2]||bc.error(a[0]),a[3]=+(a[3]?a[4]+(a[5]||1):2*(a[2]==="even"||a[2]==="odd")),a[4]=+(a[6]+a[7]||a[2]==="odd")):a[2]&&bc.error(a[0]),a},PSEUDO:function(a){var b,c;if(W.CHILD.test(a[0]))return null;if(a[3])a[2]=a[3];else if(b=a[4])O.test(b)&&(c=bh(b,!0))&&(c=b.indexOf(")",b.length-c)-b.length)&&(b=b.slice(0,c),a[0]=a[0].slice(0,c)),a[2]=b;return a.slice(0,3)}},filter:{ID:d?function(a){return a=a.replace(V,""),function(b){return b.getAttribute("id")===a}}:function(a){return a=a.replace(V,""),function(b){var c=typeof b.getAttributeNode!==n&&b.getAttributeNode("id");return c&&c.value===a}},TAG:function(a){return a==="*"?function(){return!0}:(a=a.replace(V,"").toLowerCase(),function(b){return b.nodeName&&b.nodeName.toLowerCase()===a})},CLASS:function(a){var b=B[o][a];return b||(b=B(a,new RegExp("(^|"+E+")"+a+"("+E+"|$)"))),function(a){return b.test(a.className||typeof a.getAttribute!==n&&a.getAttribute("class")||"")}},ATTR:function(a,b,c){return function(d,e){var f=bc.attr(d,a);return f==null?b==="!=":b?(f+="",b==="="?f===c:b==="!="?f!==c:b==="^="?c&&f.indexOf(c)===0:b==="*="?c&&f.indexOf(c)>-1:b==="$="?c&&f.substr(f.length-c.length)===c:b==="~="?(" "+f+" ").indexOf(c)>-1:b==="|="?f===c||f.substr(0,c.length+1)===c+"-":!1):!0}},CHILD:function(a,b,c,d){return a==="nth"?function(a){var b,e,f=a.parentNode;if(c===1&&d===0)return!0;if(f){e=0;for(b=f.firstChild;b;b=b.nextSibling)if(b.nodeType===1){e++;if(a===b)break}}return e-=d,e===c||e%c===0&&e/c>=0}:function(b){var c=b;switch(a){case"only":case"first":while(c=c.previousSibling)if(c.nodeType===1)return!1;if(a==="first")return!0;c=b;case"last":while(c=c.nextSibling)if(c.nodeType===1)return!1;return!0}}},PSEUDO:function(a,b){var c,d=e.pseudos[a]||e.setFilters[a.toLowerCase()]||bc.error("unsupported pseudo: "+a);return d[o]?d(b):d.length>1?(c=[a,a,"",b],e.setFilters.hasOwnProperty(a.toLowerCase())?z(function(a,c){var e,f=d(a,b),g=f.length;while(g--)e=y.call(a,f[g]),a[e]=!(c[e]=f[g])}):function(a){return d(a,0,c)}):d}},pseudos:{not:z(function(a){var b=[],c=[],d=i(a.replace(L,"$1"));return d[o]?z(function(a,b,c,e){var f,g=d(a,null,e,[]),h=a.length;while(h--)if(f=g[h])a[h]=!(b[h]=f)}):function(a,e,f){return b[0]=a,d(b,null,f,c),!c.pop()}}),has:z(function(a){return function(b){return bc(a,b).length>0}}),contains:z(function(a){return function(b){return(b.textContent||b.innerText||f(b)).indexOf(a)>-1}}),enabled:function(a){return a.disabled===!1},disabled:function(a){return a.disabled===!0},checked:function(a){var b=a.nodeName.toLowerCase();return b==="input"&&!!a.checked||b==="option"&&!!a.selected},selected:function(a){return a.parentNode&&a.parentNode.selectedIndex,a.selected===!0},parent:function(a){return!e.pseudos.empty(a)},empty:function(a){var b;a=a.firstChild;while(a){if(a.nodeName>"@"||(b=a.nodeType)===3||b===4)return!1;a=a.nextSibling}return!0},header:function(a){return T.test(a.nodeName)},text:function(a){var b,c;return a.nodeName.toLowerCase()==="input"&&(b=a.type)==="text"&&((c=a.getAttribute("type"))==null||c.toLowerCase()===b)},radio:bd("radio"),checkbox:bd("checkbox"),file:bd("file"),password:bd("password"),image:bd("image"),submit:be("submit"),reset:be("reset"),button:function(a){var b=a.nodeName.toLowerCase();return b==="input"&&a.type==="button"||b==="button"},input:function(a){return U.test(a.nodeName)},focus:function(a){var b=a.ownerDocument;return a===b.activeElement&&(!b.hasFocus||b.hasFocus())&&(!!a.type||!!a.href)},active:function(a){return a===a.ownerDocument.activeElement},first:bf(function(a,b,c){return[0]}),last:bf(function(a,b,c){return[b-1]}),eq:bf(function(a,b,c){return[c<0?c+b:c]}),even:bf(function(a,b,c){for(var d=0;d=0;)a.push(d);return a}),gt:bf(function(a,b,c){for(var d=c<0?c+b:c;++d ",a.querySelectorAll("[selected]").length||e.push("\\["+E+"*(?:checked|disabled|ismap|multiple|readonly|selected|value)"),a.querySelectorAll(":checked").length||e.push(":checked")}),X(function(a){a.innerHTML="
",a.querySelectorAll("[test^='']").length&&e.push("[*^$]="+E+"*(?:\"\"|'')"),a.innerHTML=" ",a.querySelectorAll(":enabled").length||e.push(":enabled",":disabled")}),e=new RegExp(e.join("|")),bp=function(a,d,f,g,h){if(!g&&!h&&(!e||!e.test(a))){var i,j,k=!0,l=o,m=d,n=d.nodeType===9&&a;if(d.nodeType===1&&d.nodeName.toLowerCase()!=="object"){i=bh(a),(k=d.getAttribute("id"))?l=k.replace(c,"\\$&"):d.setAttribute("id",l),l="[id='"+l+"'] ",j=i.length;while(j--)i[j]=l+i[j].join("");m=R.test(a)&&d.parentNode||d,n=i.join(",")}if(n)try{return w.apply(f,x.call(m.querySelectorAll(n),0)),f}catch(p){}finally{k||d.removeAttribute("id")}}return b(a,d,f,g,h)},h&&(X(function(b){a=h.call(b,"div");try{h.call(b,"[test!='']:sizzle"),f.push("!=",J)}catch(c){}}),f=new RegExp(f.join("|")),bc.matchesSelector=function(b,c){c=c.replace(d,"='$1']");if(!g(b)&&!f.test(c)&&(!e||!e.test(c)))try{var i=h.call(b,c);if(i||a||b.document&&b.document.nodeType!==11)return i}catch(j){}return bc(c,null,null,[b]).length>0})}(),e.pseudos.nth=e.pseudos.eq,e.filters=bq.prototype=e.pseudos,e.setFilters=new bq,bc.attr=p.attr,p.find=bc,p.expr=bc.selectors,p.expr[":"]=p.expr.pseudos,p.unique=bc.uniqueSort,p.text=bc.getText,p.isXMLDoc=bc.isXML,p.contains=bc.contains}(a);var bc=/Until$/,bd=/^(?:parents|prev(?:Until|All))/,be=/^.[^:#\[\.,]*$/,bf=p.expr.match.needsContext,bg={children:!0,contents:!0,next:!0,prev:!0};p.fn.extend({find:function(a){var b,c,d,e,f,g,h=this;if(typeof a!="string")return p(a).filter(function(){for(b=0,c=h.length;b0)for(e=d;e=0:p.filter(a,this).length>0:this.filter(a).length>0)},closest:function(a,b){var c,d=0,e=this.length,f=[],g=bf.test(a)||typeof a!="string"?p(a,b||this.context):0;for(;d-1:p.find.matchesSelector(c,a)){f.push(c);break}c=c.parentNode}}return f=f.length>1?p.unique(f):f,this.pushStack(f,"closest",a)},index:function(a){return a?typeof a=="string"?p.inArray(this[0],p(a)):p.inArray(a.jquery?a[0]:a,this):this[0]&&this[0].parentNode?this.prevAll().length:-1},add:function(a,b){var c=typeof a=="string"?p(a,b):p.makeArray(a&&a.nodeType?[a]:a),d=p.merge(this.get(),c);return this.pushStack(bh(c[0])||bh(d[0])?d:p.unique(d))},addBack:function(a){return this.add(a==null?this.prevObject:this.prevObject.filter(a))}}),p.fn.andSelf=p.fn.addBack,p.each({parent:function(a){var b=a.parentNode;return b&&b.nodeType!==11?b:null},parents:function(a){return p.dir(a,"parentNode")},parentsUntil:function(a,b,c){return p.dir(a,"parentNode",c)},next:function(a){return bi(a,"nextSibling")},prev:function(a){return bi(a,"previousSibling")},nextAll:function(a){return p.dir(a,"nextSibling")},prevAll:function(a){return p.dir(a,"previousSibling")},nextUntil:function(a,b,c){return p.dir(a,"nextSibling",c)},prevUntil:function(a,b,c){return p.dir(a,"previousSibling",c)},siblings:function(a){return p.sibling((a.parentNode||{}).firstChild,a)},children:function(a){return p.sibling(a.firstChild)},contents:function(a){return p.nodeName(a,"iframe")?a.contentDocument||a.contentWindow.document:p.merge([],a.childNodes)}},function(a,b){p.fn[a]=function(c,d){var e=p.map(this,b,c);return bc.test(a)||(d=c),d&&typeof d=="string"&&(e=p.filter(d,e)),e=this.length>1&&!bg[a]?p.unique(e):e,this.length>1&&bd.test(a)&&(e=e.reverse()),this.pushStack(e,a,k.call(arguments).join(","))}}),p.extend({filter:function(a,b,c){return c&&(a=":not("+a+")"),b.length===1?p.find.matchesSelector(b[0],a)?[b[0]]:[]:p.find.matches(a,b)},dir:function(a,c,d){var e=[],f=a[c];while(f&&f.nodeType!==9&&(d===b||f.nodeType!==1||!p(f).is(d)))f.nodeType===1&&e.push(f),f=f[c];return e},sibling:function(a,b){var c=[];for(;a;a=a.nextSibling)a.nodeType===1&&a!==b&&c.push(a);return c}});var bl="abbr|article|aside|audio|bdi|canvas|data|datalist|details|figcaption|figure|footer|header|hgroup|mark|meter|nav|output|progress|section|summary|time|video",bm=/ jQuery\d+="(?:null|\d+)"/g,bn=/^\s+/,bo=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi,bp=/<([\w:]+)/,bq=/ ]","i"),bv=/^(?:checkbox|radio)$/,bw=/checked\s*(?:[^=]|=\s*.checked.)/i,bx=/\/(java|ecma)script/i,by=/^\s*\s*$/g,bz={option:[1,""," "],legend:[1,""," "],thead:[1,""],tr:[2,""],td:[3,""],col:[2,""],area:[1,""," "],_default:[0,"",""]},bA=bk(e),bB=bA.appendChild(e.createElement("div"));bz.optgroup=bz.option,bz.tbody=bz.tfoot=bz.colgroup=bz.caption=bz.thead,bz.th=bz.td,p.support.htmlSerialize||(bz._default=[1,"X","
"]),p.fn.extend({text:function(a){return p.access(this,function(a){return a===b?p.text(this):this.empty().append((this[0]&&this[0].ownerDocument||e).createTextNode(a))},null,a,arguments.length)},wrapAll:function(a){if(p.isFunction(a))return this.each(function(b){p(this).wrapAll(a.call(this,b))});if(this[0]){var b=p(a,this[0].ownerDocument).eq(0).clone(!0);this[0].parentNode&&b.insertBefore(this[0]),b.map(function(){var a=this;while(a.firstChild&&a.firstChild.nodeType===1)a=a.firstChild;return a}).append(this)}return this},wrapInner:function(a){return p.isFunction(a)?this.each(function(b){p(this).wrapInner(a.call(this,b))}):this.each(function(){var b=p(this),c=b.contents();c.length?c.wrapAll(a):b.append(a)})},wrap:function(a){var b=p.isFunction(a);return this.each(function(c){p(this).wrapAll(b?a.call(this,c):a)})},unwrap:function(){return this.parent().each(function(){p.nodeName(this,"body")||p(this).replaceWith(this.childNodes)}).end()},append:function(){return this.domManip(arguments,!0,function(a){(this.nodeType===1||this.nodeType===11)&&this.appendChild(a)})},prepend:function(){return this.domManip(arguments,!0,function(a){(this.nodeType===1||this.nodeType===11)&&this.insertBefore(a,this.firstChild)})},before:function(){if(!bh(this[0]))return this.domManip(arguments,!1,function(a){this.parentNode.insertBefore(a,this)});if(arguments.length){var a=p.clean(arguments);return this.pushStack(p.merge(a,this),"before",this.selector)}},after:function(){if(!bh(this[0]))return this.domManip(arguments,!1,function(a){this.parentNode.insertBefore(a,this.nextSibling)});if(arguments.length){var a=p.clean(arguments);return this.pushStack(p.merge(this,a),"after",this.selector)}},remove:function(a,b){var c,d=0;for(;(c=this[d])!=null;d++)if(!a||p.filter(a,[c]).length)!b&&c.nodeType===1&&(p.cleanData(c.getElementsByTagName("*")),p.cleanData([c])),c.parentNode&&c.parentNode.removeChild(c);return this},empty:function(){var a,b=0;for(;(a=this[b])!=null;b++){a.nodeType===1&&p.cleanData(a.getElementsByTagName("*"));while(a.firstChild)a.removeChild(a.firstChild)}return this},clone:function(a,b){return a=a==null?!1:a,b=b==null?a:b,this.map(function(){return p.clone(this,a,b)})},html:function(a){return p.access(this,function(a){var c=this[0]||{},d=0,e=this.length;if(a===b)return c.nodeType===1?c.innerHTML.replace(bm,""):b;if(typeof a=="string"&&!bs.test(a)&&(p.support.htmlSerialize||!bu.test(a))&&(p.support.leadingWhitespace||!bn.test(a))&&!bz[(bp.exec(a)||["",""])[1].toLowerCase()]){a=a.replace(bo,"<$1>$2>");try{for(;d1&&typeof j=="string"&&bw.test(j))return this.each(function(){p(this).domManip(a,c,d)});if(p.isFunction(j))return this.each(function(e){var f=p(this);a[0]=j.call(this,e,c?f.html():b),f.domManip(a,c,d)});if(this[0]){e=p.buildFragment(a,this,k),g=e.fragment,f=g.firstChild,g.childNodes.length===1&&(g=f);if(f){c=c&&p.nodeName(f,"tr");for(h=e.cacheable||l-1;i0?this.clone(!0):this).get(),p(g[e])[b](d),f=f.concat(d);return this.pushStack(f,a,g.selector)}}),p.extend({clone:function(a,b,c){var d,e,f,g;p.support.html5Clone||p.isXMLDoc(a)||!bu.test("<"+a.nodeName+">")?g=a.cloneNode(!0):(bB.innerHTML=a.outerHTML,bB.removeChild(g=bB.firstChild));if((!p.support.noCloneEvent||!p.support.noCloneChecked)&&(a.nodeType===1||a.nodeType===11)&&!p.isXMLDoc(a)){bE(a,g),d=bF(a),e=bF(g);for(f=0;d[f];++f)e[f]&&bE(d[f],e[f])}if(b){bD(a,g);if(c){d=bF(a),e=bF(g);for(f=0;d[f];++f)bD(d[f],e[f])}}return d=e=null,g},clean:function(a,b,c,d){var f,g,h,i,j,k,l,m,n,o,q,r,s=b===e&&bA,t=[];if(!b||typeof b.createDocumentFragment=="undefined")b=e;for(f=0;(h=a[f])!=null;f++){typeof h=="number"&&(h+="");if(!h)continue;if(typeof h=="string")if(!br.test(h))h=b.createTextNode(h);else{s=s||bk(b),l=b.createElement("div"),s.appendChild(l),h=h.replace(bo,"<$1>$2>"),i=(bp.exec(h)||["",""])[1].toLowerCase(),j=bz[i]||bz._default,k=j[0],l.innerHTML=j[1]+h+j[2];while(k--)l=l.lastChild;if(!p.support.tbody){m=bq.test(h),n=i==="table"&&!m?l.firstChild&&l.firstChild.childNodes:j[1]===""&&!m?l.childNodes:[];for(g=n.length-1;g>=0;--g)p.nodeName(n[g],"tbody")&&!n[g].childNodes.length&&n[g].parentNode.removeChild(n[g])}!p.support.leadingWhitespace&&bn.test(h)&&l.insertBefore(b.createTextNode(bn.exec(h)[0]),l.firstChild),h=l.childNodes,l.parentNode.removeChild(l)}h.nodeType?t.push(h):p.merge(t,h)}l&&(h=l=s=null);if(!p.support.appendChecked)for(f=0;(h=t[f])!=null;f++)p.nodeName(h,"input")?bG(h):typeof h.getElementsByTagName!="undefined"&&p.grep(h.getElementsByTagName("input"),bG);if(c){q=function(a){if(!a.type||bx.test(a.type))return d?d.push(a.parentNode?a.parentNode.removeChild(a):a):c.appendChild(a)};for(f=0;(h=t[f])!=null;f++)if(!p.nodeName(h,"script")||!q(h))c.appendChild(h),typeof h.getElementsByTagName!="undefined"&&(r=p.grep(p.merge([],h.getElementsByTagName("script")),q),t.splice.apply(t,[f+1,0].concat(r)),f+=r.length)}return t},cleanData:function(a,b){var c,d,e,f,g=0,h=p.expando,i=p.cache,j=p.support.deleteExpando,k=p.event.special;for(;(e=a[g])!=null;g++)if(b||p.acceptData(e)){d=e[h],c=d&&i[d];if(c){if(c.events)for(f in c.events)k[f]?p.event.remove(e,f):p.removeEvent(e,f,c.handle);i[d]&&(delete i[d],j?delete e[h]:e.removeAttribute?e.removeAttribute(h):e[h]=null,p.deletedIds.push(d))}}}}),function(){var a,b;p.uaMatch=function(a){a=a.toLowerCase();var b=/(chrome)[ \/]([\w.]+)/.exec(a)||/(webkit)[ \/]([\w.]+)/.exec(a)||/(opera)(?:.*version|)[ \/]([\w.]+)/.exec(a)||/(msie) ([\w.]+)/.exec(a)||a.indexOf("compatible")<0&&/(mozilla)(?:.*? rv:([\w.]+)|)/.exec(a)||[];return{browser:b[1]||"",version:b[2]||"0"}},a=p.uaMatch(g.userAgent),b={},a.browser&&(b[a.browser]=!0,b.version=a.version),b.chrome?b.webkit=!0:b.webkit&&(b.safari=!0),p.browser=b,p.sub=function(){function a(b,c){return new a.fn.init(b,c)}p.extend(!0,a,this),a.superclass=this,a.fn=a.prototype=this(),a.fn.constructor=a,a.sub=this.sub,a.fn.init=function c(c,d){return d&&d instanceof p&&!(d instanceof a)&&(d=a(d)),p.fn.init.call(this,c,d,b)},a.fn.init.prototype=a.fn;var b=a(e);return a}}();var bH,bI,bJ,bK=/alpha\([^)]*\)/i,bL=/opacity=([^)]*)/,bM=/^(top|right|bottom|left)$/,bN=/^(none|table(?!-c[ea]).+)/,bO=/^margin/,bP=new RegExp("^("+q+")(.*)$","i"),bQ=new RegExp("^("+q+")(?!px)[a-z%]+$","i"),bR=new RegExp("^([-+])=("+q+")","i"),bS={},bT={position:"absolute",visibility:"hidden",display:"block"},bU={letterSpacing:0,fontWeight:400},bV=["Top","Right","Bottom","Left"],bW=["Webkit","O","Moz","ms"],bX=p.fn.toggle;p.fn.extend({css:function(a,c){return p.access(this,function(a,c,d){return d!==b?p.style(a,c,d):p.css(a,c)},a,c,arguments.length>1)},show:function(){return b$(this,!0)},hide:function(){return b$(this)},toggle:function(a,b){var c=typeof a=="boolean";return p.isFunction(a)&&p.isFunction(b)?bX.apply(this,arguments):this.each(function(){(c?a:bZ(this))?p(this).show():p(this).hide()})}}),p.extend({cssHooks:{opacity:{get:function(a,b){if(b){var c=bH(a,"opacity");return c===""?"1":c}}}},cssNumber:{fillOpacity:!0,fontWeight:!0,lineHeight:!0,opacity:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{"float":p.support.cssFloat?"cssFloat":"styleFloat"},style:function(a,c,d,e){if(!a||a.nodeType===3||a.nodeType===8||!a.style)return;var f,g,h,i=p.camelCase(c),j=a.style;c=p.cssProps[i]||(p.cssProps[i]=bY(j,i)),h=p.cssHooks[c]||p.cssHooks[i];if(d===b)return h&&"get"in h&&(f=h.get(a,!1,e))!==b?f:j[c];g=typeof d,g==="string"&&(f=bR.exec(d))&&(d=(f[1]+1)*f[2]+parseFloat(p.css(a,c)),g="number");if(d==null||g==="number"&&isNaN(d))return;g==="number"&&!p.cssNumber[i]&&(d+="px");if(!h||!("set"in h)||(d=h.set(a,d,e))!==b)try{j[c]=d}catch(k){}},css:function(a,c,d,e){var f,g,h,i=p.camelCase(c);return c=p.cssProps[i]||(p.cssProps[i]=bY(a.style,i)),h=p.cssHooks[c]||p.cssHooks[i],h&&"get"in h&&(f=h.get(a,!0,e)),f===b&&(f=bH(a,c)),f==="normal"&&c in bU&&(f=bU[c]),d||e!==b?(g=parseFloat(f),d||p.isNumeric(g)?g||0:f):f},swap:function(a,b,c){var d,e,f={};for(e in b)f[e]=a.style[e],a.style[e]=b[e];d=c.call(a);for(e in b)a.style[e]=f[e];return d}}),a.getComputedStyle?bH=function(b,c){var d,e,f,g,h=a.getComputedStyle(b,null),i=b.style;return h&&(d=h[c],d===""&&!p.contains(b.ownerDocument,b)&&(d=p.style(b,c)),bQ.test(d)&&bO.test(c)&&(e=i.width,f=i.minWidth,g=i.maxWidth,i.minWidth=i.maxWidth=i.width=d,d=h.width,i.width=e,i.minWidth=f,i.maxWidth=g)),d}:e.documentElement.currentStyle&&(bH=function(a,b){var c,d,e=a.currentStyle&&a.currentStyle[b],f=a.style;return e==null&&f&&f[b]&&(e=f[b]),bQ.test(e)&&!bM.test(b)&&(c=f.left,d=a.runtimeStyle&&a.runtimeStyle.left,d&&(a.runtimeStyle.left=a.currentStyle.left),f.left=b==="fontSize"?"1em":e,e=f.pixelLeft+"px",f.left=c,d&&(a.runtimeStyle.left=d)),e===""?"auto":e}),p.each(["height","width"],function(a,b){p.cssHooks[b]={get:function(a,c,d){if(c)return a.offsetWidth===0&&bN.test(bH(a,"display"))?p.swap(a,bT,function(){return cb(a,b,d)}):cb(a,b,d)},set:function(a,c,d){return b_(a,c,d?ca(a,b,d,p.support.boxSizing&&p.css(a,"boxSizing")==="border-box"):0)}}}),p.support.opacity||(p.cssHooks.opacity={get:function(a,b){return bL.test((b&&a.currentStyle?a.currentStyle.filter:a.style.filter)||"")?.01*parseFloat(RegExp.$1)+"":b?"1":""},set:function(a,b){var c=a.style,d=a.currentStyle,e=p.isNumeric(b)?"alpha(opacity="+b*100+")":"",f=d&&d.filter||c.filter||"";c.zoom=1;if(b>=1&&p.trim(f.replace(bK,""))===""&&c.removeAttribute){c.removeAttribute("filter");if(d&&!d.filter)return}c.filter=bK.test(f)?f.replace(bK,e):f+" "+e}}),p(function(){p.support.reliableMarginRight||(p.cssHooks.marginRight={get:function(a,b){return p.swap(a,{display:"inline-block"},function(){if(b)return bH(a,"marginRight")})}}),!p.support.pixelPosition&&p.fn.position&&p.each(["top","left"],function(a,b){p.cssHooks[b]={get:function(a,c){if(c){var d=bH(a,b);return bQ.test(d)?p(a).position()[b]+"px":d}}}})}),p.expr&&p.expr.filters&&(p.expr.filters.hidden=function(a){return a.offsetWidth===0&&a.offsetHeight===0||!p.support.reliableHiddenOffsets&&(a.style&&a.style.display||bH(a,"display"))==="none"},p.expr.filters.visible=function(a){return!p.expr.filters.hidden(a)}),p.each({margin:"",padding:"",border:"Width"},function(a,b){p.cssHooks[a+b]={expand:function(c){var d,e=typeof c=="string"?c.split(" "):[c],f={};for(d=0;d<4;d++)f[a+bV[d]+b]=e[d]||e[d-2]||e[0];return f}},bO.test(a)||(p.cssHooks[a+b].set=b_)});var cd=/%20/g,ce=/\[\]$/,cf=/\r?\n/g,cg=/^(?:color|date|datetime|datetime-local|email|hidden|month|number|password|range|search|tel|text|time|url|week)$/i,ch=/^(?:select|textarea)/i;p.fn.extend({serialize:function(){return p.param(this.serializeArray())},serializeArray:function(){return this.map(function(){return this.elements?p.makeArray(this.elements):this}).filter(function(){return this.name&&!this.disabled&&(this.checked||ch.test(this.nodeName)||cg.test(this.type))}).map(function(a,b){var c=p(this).val();return c==null?null:p.isArray(c)?p.map(c,function(a,c){return{name:b.name,value:a.replace(cf,"\r\n")}}):{name:b.name,value:c.replace(cf,"\r\n")}}).get()}}),p.param=function(a,c){var d,e=[],f=function(a,b){b=p.isFunction(b)?b():b==null?"":b,e[e.length]=encodeURIComponent(a)+"="+encodeURIComponent(b)};c===b&&(c=p.ajaxSettings&&p.ajaxSettings.traditional);if(p.isArray(a)||a.jquery&&!p.isPlainObject(a))p.each(a,function(){f(this.name,this.value)});else for(d in a)ci(d,a[d],c,f);return e.join("&").replace(cd,"+")};var cj,ck,cl=/#.*$/,cm=/^(.*?):[ \t]*([^\r\n]*)\r?$/mg,cn=/^(?:about|app|app\-storage|.+\-extension|file|res|widget):$/,co=/^(?:GET|HEAD)$/,cp=/^\/\//,cq=/\?/,cr=/
{{.Title}}
{{with .Subtitle}}{{.}}{{end}}
{{if .Authors}}
{{range .Authors}}
{{range .Elem}}{{elem $.Template .}}{{end}}
{{end}}
{{end}}
{{with .Sections}}
Contents
{{template "TOC" .}}
{{end}}
{{range .Sections}}
{{elem $.Template .}}
{{end}}{{/* of Section block */}}
{{if .PlayEnabled}}
{{end}}
{{end}}
{{define "TOC"}}
{{range .}}
{{.Title}}
{{with .Sections}}{{template "TOC-Inner" .}}{{end}}
{{end}}
{{end}}
{{define "TOC-Inner"}}
{{range .}}
{{.Title}}
{{with .Sections}}{{template "TOC-Inner" .}}{{end}}
{{end}}
{{end}}
{{define "newline"}}
{{/* No automatic line break. Paragraphs are free-form. */}}
{{end}}
================================================
FILE: cmd/present/templates/dir.tmpl
================================================
Talks - The Go Programming Language
Go talks
{{with .Path}}
{{.}} {{end}}
{{with .Articles}}
Articles:
{{range .}}
{{.Name}} : {{.Title}}
{{end}}
{{end}}
{{with .Slides}}
Slide decks:
{{range .}}
{{.Name}} : {{.Title}}
{{end}}
{{end}}
{{with .Other}}
Files:
{{range .}}
{{.Name}}
{{end}}
{{end}}
{{with .Dirs}}
Sub-directories:
{{range .}}
{{.Name}}
{{end}}
{{end}}
================================================
FILE: cmd/present/templates/slides.tmpl
================================================
{/* This is the slide template. It defines how presentations are formatted. */}
{{define "root"}}
{{.Title}}
{{if .NotesEnabled}}
{{end}}
{{.Title}}
{{with .Subtitle}}{{.}} {{end}}
{{if not .Time.IsZero}}{{.Time.Format "2 January 2006"}} {{end}}
{{range .Authors}}
{{range .TextElem}}{{elem $.Template .}}{{end}}
{{end}}
{{range $i, $s := .Sections}}
{{if $s.Elem}}
{{$s.Title}}
{{range $s.Elem}}{{elem $.Template .}}{{end}}
{{else}}
{{$s.Title}}
{{end}}
{{pagenum $s 1}}
{{end}}{{/* of Slide block */}}
Thank you
{{range .Authors}}
{{range .Elem}}{{elem $.Template .}}{{end}}
{{end}}
Use the left and right arrow keys or click the left and right
edges of the page to navigate between slides.
(Press 'H' or navigate to hide this message.)
{{if .PlayEnabled}}
{{end}}
{{end}}
{{define "newline"}}
{{end}}
================================================
FILE: cmd/present2md/main.go
================================================
// Copyright 2020 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Present2md converts legacy-syntax present files to Markdown-syntax present files.
//
// Usage:
//
// present2md [-w] [file ...]
//
// By default, present2md prints the Markdown-syntax form of each input file to standard output.
// If no input file is listed, standard input is used.
//
// The -w flag causes present2md to update the files in place, overwriting each with its
// Markdown-syntax equivalent.
//
// Examples
//
// present2md your.article
// present2md -w *.article
package main
import (
"bytes"
"flag"
"fmt"
"io"
"log"
"net/url"
"os"
"strings"
"unicode"
"unicode/utf8"
"golang.org/x/tools/present"
)
func usage() {
fmt.Fprintf(os.Stderr, "usage: present2md [-w] [file ...]\n")
os.Exit(2)
}
var (
writeBack = flag.Bool("w", false, "write conversions back to original files")
exitStatus = 0
)
func main() {
log.SetPrefix("present2md: ")
log.SetFlags(0)
flag.Usage = usage
flag.Parse()
args := flag.Args()
if len(args) == 0 {
if *writeBack {
log.Fatalf("cannot use -w with standard input")
}
convert(os.Stdin, "stdin", false)
return
}
for _, arg := range args {
f, err := os.Open(arg)
if err != nil {
log.Print(err)
exitStatus = 1
continue
}
err = convert(f, arg, *writeBack)
f.Close()
if err != nil {
log.Print(err)
exitStatus = 1
}
}
os.Exit(exitStatus)
}
// convert reads the data from r, parses it as legacy present,
// and converts it to Markdown-enabled present.
// If any errors occur, the data is reported as coming from file.
// If writeBack is true, the converted version is written back to file.
// If writeBack is false, the converted version is printed to standard output.
func convert(r io.Reader, file string, writeBack bool) error {
data, err := io.ReadAll(r)
if err != nil {
return err
}
if bytes.HasPrefix(data, []byte("# ")) {
return fmt.Errorf("%v: already markdown", file)
}
// Convert all comments before parsing the document.
// The '//' comment is treated as normal text and so
// is passed through the translation unaltered.
data = bytes.Replace(data, []byte("\n#"), []byte("\n//"), -1)
doc, err := present.Parse(bytes.NewReader(data), file, 0)
if err != nil {
return err
}
// Title and Subtitle, Time, Tags.
var md bytes.Buffer
fmt.Fprintf(&md, "# %s\n", doc.Title)
if doc.Subtitle != "" {
fmt.Fprintf(&md, "%s\n", doc.Subtitle)
}
if !doc.Time.IsZero() {
fmt.Fprintf(&md, "%s\n", doc.Time.Format("2 Jan 2006"))
}
if len(doc.Tags) > 0 {
fmt.Fprintf(&md, "Tags: %s\n", strings.Join(doc.Tags, ", "))
}
// Summary, defaulting to first paragraph of section.
// (Summaries must be explicit for Markdown-enabled present,
// and the expectation is that they will be shorter than the
// whole first paragraph. But this is what the blog does today.)
if strings.HasSuffix(file, ".article") && len(doc.Sections) > 0 {
for _, elem := range doc.Sections[0].Elem {
text, ok := elem.(present.Text)
if !ok || text.Pre {
// skip everything but non-text elements
continue
}
fmt.Fprintf(&md, "Summary:")
for _, line := range text.Lines {
fmt.Fprintf(&md, " ")
printStyled(&md, line)
}
fmt.Fprintf(&md, "\n")
break
}
}
// Authors
for _, a := range doc.Authors {
fmt.Fprintf(&md, "\n")
for _, elem := range a.Elem {
switch elem := elem.(type) {
default:
// Can only happen if this type switch is incomplete, which is a bug.
log.Fatalf("%s: unexpected author type %T", file, elem)
case present.Text:
for _, line := range elem.Lines {
fmt.Fprintf(&md, "%s\n", markdownEscape(line))
}
case present.Link:
fmt.Fprintf(&md, "%s\n", markdownEscape(elem.Label))
}
}
}
// Invariant: the output ends in non-blank line now,
// and after printing any piece of the file below,
// the output should still end in a non-blank line.
// If a blank line separator is needed, it should be printed
// before the block that needs separating, not after.
if len(doc.TitleNotes) > 0 {
fmt.Fprintf(&md, "\n")
for _, line := range doc.TitleNotes {
fmt.Fprintf(&md, ": %s\n", line)
}
}
if len(doc.Sections) == 1 && strings.HasSuffix(file, ".article") {
// Blog drops section headers when there is only one section.
// Don't print a title in this case, to make clear that it's being dropped.
fmt.Fprintf(&md, "\n##\n")
printSectionBody(file, 1, &md, doc.Sections[0].Elem)
} else {
for _, s := range doc.Sections {
fmt.Fprintf(&md, "\n")
fmt.Fprintf(&md, "## %s\n", markdownEscape(s.Title))
printSectionBody(file, 1, &md, s.Elem)
}
}
if !writeBack {
os.Stdout.Write(md.Bytes())
return nil
}
return os.WriteFile(file, md.Bytes(), 0666)
}
func printSectionBody(file string, depth int, w *bytes.Buffer, elems []present.Elem) {
for _, elem := range elems {
switch elem := elem.(type) {
default:
// Can only happen if this type switch is incomplete, which is a bug.
log.Fatalf("%s: unexpected present element type %T", file, elem)
case present.Text:
fmt.Fprintf(w, "\n")
lines := elem.Lines
for len(lines) > 0 && lines[0] == "" {
lines = lines[1:]
}
if elem.Pre {
for line := range strings.SplitSeq(strings.TrimRight(elem.Raw, "\n"), "\n") {
if line == "" {
fmt.Fprintf(w, "\n")
} else {
fmt.Fprintf(w, "\t%s\n", line)
}
}
} else {
for _, line := range elem.Lines {
printStyled(w, line)
fmt.Fprintf(w, "\n")
}
}
case present.List:
fmt.Fprintf(w, "\n")
for _, item := range elem.Bullet {
fmt.Fprintf(w, " - ")
for i, line := range strings.Split(item, "\n") {
if i > 0 {
fmt.Fprintf(w, " ")
}
printStyled(w, line)
fmt.Fprintf(w, "\n")
}
}
case present.Section:
fmt.Fprintf(w, "\n")
sep := " "
if elem.Title == "" {
sep = ""
}
fmt.Fprintf(w, "%s%s%s\n", strings.Repeat("#", depth+2), sep, markdownEscape(elem.Title))
printSectionBody(file, depth+1, w, elem.Elem)
case interface{ PresentCmd() string }:
// If there are multiple present commands in a row, don't print a blank line before the second etc.
b := w.Bytes()
sep := "\n"
if len(b) > 0 {
i := bytes.LastIndexByte(b[:len(b)-1], '\n')
if b[i+1] == '.' {
sep = ""
}
}
fmt.Fprintf(w, "%s%s\n", sep, elem.PresentCmd())
}
}
}
func markdownEscape(s string) string {
var b strings.Builder
for i, r := range s {
switch {
case r == '#' && i == 0,
r == '*',
r == '_',
r == '<' && (i == 0 || s[i-1] != ' ') && i+1 < len(s) && s[i+1] != ' ',
r == '[' && strings.Contains(s[i:], "]("):
b.WriteRune('\\')
}
b.WriteRune(r)
}
return b.String()
}
// Copy of ../../present/style.go adjusted to produce Markdown instead of HTML.
/*
Fonts are demarcated by an initial and final char bracketing a
space-delimited word, plus possibly some terminal punctuation.
The chars are
_ for italic
* for bold
` (back quote) for fixed width.
Inner appearances of the char become spaces. For instance,
_this_is_italic_!
becomes
this is italic !
*/
func printStyled(w *bytes.Buffer, text string) {
w.WriteString(font(text))
}
// font returns s with font indicators turned into HTML font tags.
func font(s string) string {
if !strings.ContainsAny(s, "[`_*") {
return markdownEscape(s)
}
words := split(s)
var b bytes.Buffer
Word:
for w, word := range words {
words[w] = markdownEscape(word) // for all the continue Word
if len(word) < 2 {
continue Word
}
if link, _ := parseInlineLink(word); link != "" {
words[w] = link
continue Word
}
const marker = "_*`"
// Initial punctuation is OK but must be peeled off.
first := strings.IndexAny(word, marker)
if first == -1 {
continue Word
}
// Opening marker must be at the beginning of the token or else preceded by punctuation.
if first != 0 {
r, _ := utf8.DecodeLastRuneInString(word[:first])
if !unicode.IsPunct(r) {
continue Word
}
}
open, word := markdownEscape(word[:first]), word[first:]
char := word[0] // ASCII is OK.
close := ""
switch char {
default:
continue Word
case '_':
open += "_"
close = "_"
case '*':
open += "**"
close = "**"
case '`':
open += "`"
close = "`"
}
// Closing marker must be at the end of the token or else followed by punctuation.
last := strings.LastIndex(word, word[:1])
if last == 0 {
continue Word
}
if last+1 != len(word) {
r, _ := utf8.DecodeRuneInString(word[last+1:])
if !unicode.IsPunct(r) {
continue Word
}
}
head, tail := word[:last+1], word[last+1:]
b.Reset()
var wid int
for i := 1; i < len(head)-1; i += wid {
var r rune
r, wid = utf8.DecodeRuneInString(head[i:])
if r != rune(char) {
// Ordinary character.
b.WriteRune(r)
continue
}
if head[i+1] != char {
// Inner char becomes space.
b.WriteRune(' ')
continue
}
// Doubled char becomes real char.
// Not worth worrying about "_x__".
b.WriteByte(char)
wid++ // Consumed two chars, both ASCII.
}
text := b.String()
if close == "`" {
for strings.Contains(text, close) {
open += "`"
close += "`"
}
} else {
text = markdownEscape(text)
}
words[w] = open + text + close + tail
}
return strings.Join(words, "")
}
// split is like strings.Fields but also returns the runs of spaces
// and treats inline links as distinct words.
func split(s string) []string {
var (
words = make([]string, 0, 10)
start = 0
)
// appendWord appends the string s[start:end] to the words slice.
// If the word contains the beginning of a link, the non-link portion
// of the word and the entire link are appended as separate words,
// and the start index is advanced to the end of the link.
appendWord := func(end int) {
if j := strings.Index(s[start:end], "[["); j > -1 {
if _, l := parseInlineLink(s[start+j:]); l > 0 {
// Append portion before link, if any.
if j > 0 {
words = append(words, s[start:start+j])
}
// Append link itself.
words = append(words, s[start+j:start+j+l])
// Advance start index to end of link.
start = start + j + l
return
}
}
// No link; just add the word.
words = append(words, s[start:end])
start = end
}
wasSpace := false
for i, r := range s {
isSpace := unicode.IsSpace(r)
if i > start && isSpace != wasSpace {
appendWord(i)
}
wasSpace = isSpace
}
for start < len(s) {
appendWord(len(s))
}
return words
}
// parseInlineLink parses an inline link at the start of s, and returns
// a rendered Markdown link and the total length of the raw inline link.
// If no inline link is present, it returns all zeroes.
func parseInlineLink(s string) (link string, length int) {
if !strings.HasPrefix(s, "[[") {
return
}
end := strings.Index(s, "]]")
if end == -1 {
return
}
urlEnd := strings.Index(s, "]")
rawURL := s[2:urlEnd]
const badURLChars = `<>"{}|\^[] ` + "`" // per RFC2396 section 2.4.3
if strings.ContainsAny(rawURL, badURLChars) {
return
}
if urlEnd == end {
simpleURL := ""
url, err := url.Parse(rawURL)
if err == nil {
// If the URL is http://foo.com, drop the http://
// In other words, render [[http://golang.org]] as:
// golang.org
if after, ok := strings.CutPrefix(rawURL, url.Scheme+"://"); ok {
simpleURL = after
} else if after, ok := strings.CutPrefix(rawURL, url.Scheme+":"); ok {
simpleURL = after
}
}
return renderLink(rawURL, simpleURL), end + 2
}
if s[urlEnd:urlEnd+2] != "][" {
return
}
text := s[urlEnd+2 : end]
return renderLink(rawURL, text), end + 2
}
func renderLink(href, text string) string {
text = font(text)
if text == "" {
text = markdownEscape(href)
}
return "[" + text + "](" + href + ")"
}
================================================
FILE: cmd/signature-fuzzer/README.md
================================================
# signature-fuzzer
This directory contains utilities for fuzz testing of Go function signatures, for use in developing/testing a Go compiler.
The basic idea of the fuzzer is that it emits source code for a stand-alone Go program; this generated program is a series of pairs of functions, a "Caller" function and a "Checker" function. The signature of the Checker function is generated randomly (random number of parameters and returns, each with randomly chosen types). The "Caller" func contains invocations of the "Checker" function, each passing randomly chosen values to the params of the "Checker", then the caller verifies that expected values are returned correctly. The "Checker" function in turn has code to verify that the expected values (more details below).
There are three main parts to the fuzzer: a generator package, a driver package, and a runner package.
The "generator" contains the guts of the fuzzer, the bits that actually emit the random code.
The "driver" is a stand-alone program that invokes the generator to create a single test program. It is not terribly useful on its own (since it doesn't actually build or run the generated program), but it is handy for debugging the generator or looking at examples of the emitted code.
The "runner" is a more complete test harness; it repeatedly runs the generator to create a new test program, builds the test program, then runs it (checking for errors along the way). If at any point a build or test fails, the "runner" harness attempts a minimization process to try to narrow down the failure to a single package and/or function.
## What the generated code looks like
Generated Go functions will have an "interesting" set of signatures (mix of
arrays, scalars, structs), intended to pick out corner cases and odd bits in the
Go compiler's code that handles function calls and returns.
The first generated file is genChecker.go, which contains function that look something
like this (simplified):
```
type StructF4S0 struct {
F0 float64
F1 int16
F2 uint16
}
// 0 returns 2 params
func Test4(p0 int8, p1 StructF4S0) {
c0 := int8(-1)
if p0 != c0 {
NoteFailure(4, "parm", 0)
}
c1 := StructF4S0{float64(2), int16(-3), uint16(4)}
if p1 != c1 {
NoteFailure(4, "parm", 1)
}
return
}
```
Here the test generator has randomly selected 0 return values and 2 params, then randomly generated types for the params.
The generator then emits code on the calling side into the file "genCaller.go", which might look like:
```
func Caller4() {
var p0 int8
p0 = int8(-1)
var p1 StructF4S0
p1 = StructF4S0{float64(2), int16(-3), uint16(4)}
// 0 returns 2 params
Test4(p0, p1)
}
```
The generator then emits some utility functions (ex: NoteFailure) and a main routine that cycles through all of the tests.
## Trying a single run of the generator
To generate a set of source files just to see what they look like, you can build and run the test generator as follows. This creates a new directory "cabiTest" containing generated test files:
```
$ git clone https://golang.org/x/tools
$ cd tools/cmd/signature-fuzzer/fuzz-driver
$ go build .
$ ./fuzz-driver -numpkgs 3 -numfcns 5 -seed 12345 -outdir /tmp/sigfuzzTest -pkgpath foobar
$ cd /tmp/sigfuzzTest
$ find . -type f -print
./genCaller1/genCaller1.go
./genUtils/genUtils.go
./genChecker1/genChecker1.go
./genChecker0/genChecker0.go
./genCaller2/genCaller2.go
./genCaller0/genCaller0.go
./genMain.go
./go.mod
./genChecker2/genChecker2.go
$
```
You can build and run the generated files in the usual way:
```
$ cd /tmp/sigfuzzTest
$ go build .
$ ./foobar
starting main
finished 15 tests
$
```
## Example usage for the test runner
The test runner orchestrates multiple runs of the fuzzer, iteratively emitting code, building it, and testing the resulting binary. To use the runner, build and invoke it with a specific number of iterations; it will select a new random seed on each invocation. The runner will terminate as soon as it finds a failure. Example:
```
$ git clone https://golang.org/x/tools
$ cd tools/cmd/signature-fuzzer/fuzz-runner
$ go build .
$ ./fuzz-runner -numit=3
... begin iteration 0 with current seed 67104558
starting main
finished 1000 tests
... begin iteration 1 with current seed 67104659
starting main
finished 1000 tests
... begin iteration 2 with current seed 67104760
starting main
finished 1000 tests
$
```
If the runner encounters a failure, it will try to perform test-case "minimization", e.g. attempt to isolate the failure
```
$ cd tools/cmd/signature-fuzzer/fuzz-runner
$ go build .
$ ./fuzz-runner -n=10
./fuzz-runner -n=10
... begin iteration 0 with current seed 40661762
Error: fail [reflect] |20|3|1| =Checker3.Test1= return 1
error executing cmd ./fzTest: exit status 1
... starting minimization for failed directory /tmp/fuzzrun1005327337/fuzzTest
package minimization succeeded: found bad pkg 3
function minimization succeeded: found bad fcn 1
$
```
Here the runner has generates a failure, minimized it down to a single function and package, and left the resulting program in the output directory /tmp/fuzzrun1005327337/fuzzTest.
## Limitations, future work
No support yet for variadic functions.
The set of generated types is still a bit thin; it has fairly limited support for interface values, and doesn't include channels.
Todos:
- better interface value coverage
- implement testing of reflect.MakeFunc
- extend to work with generic code of various types
- extend to work in a debugging scenario (e.g. instead of just emitting code,
emit a script of debugger commands to run the program with expected
responses from the debugger)
- rework things so that instead of always checking all of a given parameter
value, we sometimes skip over elements (or just check the length of a slice
or string as opposed to looking at its value)
- consider adding runtime.GC() calls at some points in the generated code
================================================
FILE: cmd/signature-fuzzer/fuzz-driver/driver.go
================================================
// Copyright 2021 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Stand-alone driver for emitting function-signature test code. This
// program is mainly just a wrapper around the code that lives in the
// fuzz-generator package; it is useful for generating a specific bad
// code scenario for a given seed, or for doing development on the
// fuzzer, but for doing actual fuzz testing, better to use
// fuzz-runner.
package main
import (
"flag"
"fmt"
"log"
"math/rand"
"os"
"time"
generator "golang.org/x/tools/cmd/signature-fuzzer/internal/fuzz-generator"
)
// Basic options
var numfcnflag = flag.Int("numfcns", 10, "Number of test func pairs to emit in each package")
var numpkgflag = flag.Int("numpkgs", 1, "Number of test packages to emit")
var seedflag = flag.Int64("seed", -1, "Random seed")
var tagflag = flag.String("tag", "gen", "Prefix name of go files/pkgs to generate")
var outdirflag = flag.String("outdir", "", "Output directory for generated files")
var pkgpathflag = flag.String("pkgpath", "gen", "Base package path for generated files")
// Options used for test case minimization.
var fcnmaskflag = flag.String("fcnmask", "", "Mask containing list of fcn numbers to emit")
var pkmaskflag = flag.String("pkgmask", "", "Mask containing list of pkg numbers to emit")
// Options used to control which features are used in the generated code.
var reflectflag = flag.Bool("reflect", true, "Include testing of reflect.Call.")
var deferflag = flag.Bool("defer", true, "Include testing of defer stmts.")
var recurflag = flag.Bool("recur", true, "Include testing of recursive calls.")
var takeaddrflag = flag.Bool("takeaddr", true, "Include functions that take the address of their parameters and results.")
var methodflag = flag.Bool("method", true, "Include testing of method calls.")
var inlimitflag = flag.Int("inmax", -1, "Max number of input params.")
var outlimitflag = flag.Int("outmax", -1, "Max number of input params.")
var pragmaflag = flag.String("pragma", "", "Tag generated test routines with pragma //go:.")
var maxfailflag = flag.Int("maxfail", 10, "Maximum runtime failures before test self-terminates")
var stackforceflag = flag.Bool("forcestackgrowth", true, "Use hooks to force stack growth.")
// Debugging options
var verbflag = flag.Int("v", 0, "Verbose trace output level")
// Debugging/testing options. These tell the generator to emit "bad" code so as to
// test the logic for detecting errors and/or minimization (in the fuzz runner).
var emitbadflag = flag.Int("emitbad", 0, "[Testing only] force generator to emit 'bad' code.")
var selbadpkgflag = flag.Int("badpkgidx", 0, "[Testing only] select index of bad package (used with -emitbad)")
var selbadfcnflag = flag.Int("badfcnidx", 0, "[Testing only] select index of bad function (used with -emitbad)")
// Misc options
var goimpflag = flag.Bool("goimports", false, "Run 'goimports' on generated code.")
var randctlflag = flag.Int("randctl", generator.RandCtlChecks|generator.RandCtlPanic, "Wraprand control flag")
func verb(vlevel int, s string, a ...any) {
if *verbflag >= vlevel {
fmt.Printf(s, a...)
fmt.Printf("\n")
}
}
func usage(msg string) {
if len(msg) > 0 {
fmt.Fprintf(os.Stderr, "error: %s\n", msg)
}
fmt.Fprintf(os.Stderr, "usage: fuzz-driver [flags]\n\n")
flag.PrintDefaults()
fmt.Fprintf(os.Stderr, "Example:\n\n")
fmt.Fprintf(os.Stderr, " fuzz-driver -numpkgs=23 -numfcns=19 -seed 10101 -outdir gendir\n\n")
fmt.Fprintf(os.Stderr, " \tgenerates a Go program with 437 test cases (23 packages, each \n")
fmt.Fprintf(os.Stderr, " \twith 19 functions, for a total of 437 funcs total) into a set of\n")
fmt.Fprintf(os.Stderr, " \tsub-directories in 'gendir', using random see 10101\n")
os.Exit(2)
}
func setupTunables() {
tunables := generator.DefaultTunables()
if !*reflectflag {
tunables.DisableReflectionCalls()
}
if !*deferflag {
tunables.DisableDefer()
}
if !*recurflag {
tunables.DisableRecursiveCalls()
}
if !*takeaddrflag {
tunables.DisableTakeAddr()
}
if !*methodflag {
tunables.DisableMethodCalls()
}
if *inlimitflag != -1 {
tunables.LimitInputs(*inlimitflag)
}
if *outlimitflag != -1 {
tunables.LimitOutputs(*outlimitflag)
}
generator.SetTunables(tunables)
}
func main() {
log.SetFlags(0)
log.SetPrefix("fuzz-driver: ")
flag.Parse()
generator.Verbctl = *verbflag
if *outdirflag == "" {
usage("select an output directory with -o flag")
}
verb(1, "in main verblevel=%d", *verbflag)
if *seedflag == -1 {
// user has not selected a specific seed -- pick one.
now := time.Now()
*seedflag = now.UnixNano() % 123456789
verb(0, "selected seed: %d", *seedflag)
}
rand.Seed(*seedflag)
if flag.NArg() != 0 {
usage("unknown extra arguments")
}
verb(1, "tag is %s", *tagflag)
fcnmask, err := generator.ParseMaskString(*fcnmaskflag, "fcn")
if err != nil {
usage(fmt.Sprintf("mangled fcn mask arg: %v", err))
}
pkmask, err := generator.ParseMaskString(*pkmaskflag, "pkg")
if err != nil {
usage(fmt.Sprintf("mangled pkg mask arg: %v", err))
}
verb(2, "pkg mask is %v", pkmask)
verb(2, "fn mask is %v", fcnmask)
verb(1, "starting generation")
setupTunables()
config := generator.GenConfig{
PkgPath: *pkgpathflag,
Tag: *tagflag,
OutDir: *outdirflag,
NumTestPackages: *numpkgflag,
NumTestFunctions: *numfcnflag,
Seed: *seedflag,
Pragma: *pragmaflag,
FcnMask: fcnmask,
PkgMask: pkmask,
MaxFail: *maxfailflag,
ForceStackGrowth: *stackforceflag,
RandCtl: *randctlflag,
RunGoImports: *goimpflag,
EmitBad: *emitbadflag,
BadPackageIdx: *selbadpkgflag,
BadFuncIdx: *selbadfcnflag,
}
errs := generator.Generate(config)
if errs != 0 {
log.Fatal("errors during generation")
}
verb(1, "... files written to directory %s", *outdirflag)
verb(1, "leaving main")
}
================================================
FILE: cmd/signature-fuzzer/fuzz-driver/drv_test.go
================================================
// Copyright 2021 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package main
import (
"os"
"os/exec"
"path/filepath"
"runtime"
"testing"
"golang.org/x/tools/internal/testenv"
)
// buildDriver builds the fuzz-driver executable, returning its path.
func buildDriver(t *testing.T) string {
t.Helper()
if runtime.GOOS == "android" {
t.Skipf("the dependencies are not available on android")
return ""
}
bindir := filepath.Join(t.TempDir(), "bin")
err := os.Mkdir(bindir, os.ModePerm)
if err != nil {
t.Fatal(err)
}
binary := filepath.Join(bindir, "driver")
if runtime.GOOS == "windows" {
binary += ".exe"
}
cmd := exec.Command("go", "build", "-o", binary)
if err := cmd.Run(); err != nil {
t.Fatalf("Building fuzz-driver: %v", err)
}
return binary
}
func TestEndToEndIntegration(t *testing.T) {
testenv.NeedsTool(t, "go")
td := t.TempDir()
// Build the fuzz-driver binary.
// Note: if more tests are added to this package, move this to single setup fcn, so
// that we don't have to redo the build each time.
binary := buildDriver(t)
// Kick off a run.
gendir := filepath.Join(td, "gen")
args := []string{"-numfcns", "3", "-numpkgs", "1", "-seed", "101", "-outdir", gendir}
c := exec.Command(binary, args...)
b, err := c.CombinedOutput()
if err != nil {
t.Fatalf("error invoking fuzz-driver: %v\n%s", err, b)
}
found := ""
walker := func(path string, info os.FileInfo, err error) error {
found = found + ":" + info.Name()
return nil
}
// Make sure it emits something.
err2 := filepath.Walk(gendir, walker)
if err2 != nil {
t.Fatalf("error from filepath.Walk: %v", err2)
}
const expected = ":gen:genCaller0:genCaller0.go:genChecker0:genChecker0.go:genMain.go:genUtils:genUtils.go:go.mod"
if found != expected {
t.Errorf("walk of generated code: got %s want %s", found, expected)
}
}
================================================
FILE: cmd/signature-fuzzer/fuzz-runner/rnr_test.go
================================================
// Copyright 2021 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package main
import (
"fmt"
"os"
"os/exec"
"path/filepath"
"runtime"
"strings"
"testing"
"golang.org/x/tools/internal/testenv"
)
func canRace() bool {
_, err := exec.Command("go", "run", "-race", "./testdata/himom.go").CombinedOutput()
return err == nil
}
// buildRunner builds the fuzz-runner executable, returning its path.
func buildRunner(t *testing.T) string {
bindir := filepath.Join(t.TempDir(), "bin")
err := os.Mkdir(bindir, os.ModePerm)
if err != nil {
t.Fatal(err)
}
binary := filepath.Join(bindir, "runner")
if runtime.GOOS == "windows" {
binary += ".exe"
}
cmd := exec.Command("go", "build", "-o", binary)
if err := cmd.Run(); err != nil {
t.Fatalf("Building fuzz-runner: %v", err)
}
return binary
}
// TestRunner builds the binary, then kicks off a collection of sub-tests that invoke it.
func TestRunner(t *testing.T) {
testenv.NeedsTool(t, "go")
if runtime.GOOS == "android" {
t.Skipf("the dependencies are not available on android")
}
binaryPath := buildRunner(t)
// Sub-tests using the binary built above.
t.Run("Basic", func(t *testing.T) { testBasic(t, binaryPath) })
t.Run("Race", func(t *testing.T) { testRace(t, binaryPath) })
t.Run("Minimization1", func(t *testing.T) { testMinimization1(t, binaryPath) })
t.Run("Minimization2", func(t *testing.T) { testMinimization2(t, binaryPath) })
}
func testBasic(t *testing.T, binaryPath string) {
t.Parallel()
args := []string{"-numit=1", "-numfcns=1", "-numpkgs=1", "-seed=103", "-cleancache=0"}
c := exec.Command(binaryPath, args...)
b, err := c.CombinedOutput()
t.Logf("%s\n", b)
if err != nil {
t.Fatalf("error invoking fuzz-runner: %v", err)
}
}
func testRace(t *testing.T, binaryPath string) {
t.Parallel()
// For this test to work, the current test platform has to support the
// race detector. Check to see if that is the case by running a very
// simple Go program through it.
if !canRace() {
t.Skip("current platform does not appear to support the race detector")
}
args := []string{"-v=1", "-numit=1", "-race", "-numfcns=3", "-numpkgs=3", "-seed=987", "-cleancache=0"}
c := exec.Command(binaryPath, args...)
b, err := c.CombinedOutput()
t.Logf("%s\n", b)
if err != nil {
t.Fatalf("error invoking fuzz-runner: %v", err)
}
}
func testMinimization1(t *testing.T, binaryPath string) {
if binaryPath == "" {
t.Skipf("No runner binary")
}
t.Parallel()
// Fire off the runner passing it -emitbad=1, so that the generated code
// contains illegal Go code (which will force the build to fail). Verify that
// it does fail, that the error reflects the nature of the failure, and that
// we can minimize the error down to a single package.
args := []string{"-emitbad=1", "-badfcnidx=2", "-badpkgidx=2",
"-forcetmpclean", "-cleancache=0",
"-numit=1", "-numfcns=3", "-numpkgs=3", "-seed=909"}
invocation := fmt.Sprintf("%s %v", binaryPath, args)
c := exec.Command(binaryPath, args...)
b, err := c.CombinedOutput()
t.Logf("%s\n", b)
if err == nil {
t.Fatalf("unexpected pass of fuzz-runner (invocation %q): %v", invocation, err)
}
result := string(b)
if !strings.Contains(result, "syntax error") {
t.Fatalf("-emitbad=1 did not trigger syntax error (invocation %q): output: %s", invocation, result)
}
if !strings.Contains(result, "package minimization succeeded: found bad pkg 2") {
t.Fatalf("failed to minimize package (invocation %q): output: %s", invocation, result)
}
if !strings.Contains(result, "function minimization succeeded: found bad fcn 2") {
t.Fatalf("failed to minimize package (invocation %q): output: %s", invocation, result)
}
}
func testMinimization2(t *testing.T, binaryPath string) {
if binaryPath == "" {
t.Skipf("No runner binary")
}
t.Parallel()
// Fire off the runner passing it -emitbad=2, so that the
// generated code forces a runtime error. Verify that it does
// fail, and that the error is reflective.
args := []string{"-emitbad=2", "-badfcnidx=1", "-badpkgidx=1",
"-forcetmpclean", "-cleancache=0",
"-numit=1", "-numfcns=3", "-numpkgs=3", "-seed=55909"}
invocation := fmt.Sprintf("%s %v", binaryPath, args)
c := exec.Command(binaryPath, args...)
b, err := c.CombinedOutput()
t.Logf("%s\n", b)
if err == nil {
t.Fatalf("unexpected pass of fuzz-runner (invocation %q): %v", invocation, err)
}
result := string(b)
if !strings.Contains(result, "Error: fail") || !strings.Contains(result, "Checker1.Test1") {
t.Fatalf("-emitbad=2 did not trigger runtime error (invocation %q): output: %s", invocation, result)
}
if !strings.Contains(result, "package minimization succeeded: found bad pkg 1") {
t.Fatalf("failed to minimize package (invocation %q): output: %s", invocation, result)
}
if !strings.Contains(result, "function minimization succeeded: found bad fcn 1") {
t.Fatalf("failed to minimize package (invocation %q): output: %s", invocation, result)
}
}
================================================
FILE: cmd/signature-fuzzer/fuzz-runner/runner.go
================================================
// Copyright 2021 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Program for performing test runs using "fuzz-driver".
// Main loop iteratively runs "fuzz-driver" to create a corpus,
// then builds and runs the code. If a failure in the run is
// detected, then a testcase minimization phase kicks in.
package main
import (
"flag"
"fmt"
"log"
"os"
"os/exec"
"path/filepath"
"runtime"
"strconv"
"strings"
"time"
generator "golang.org/x/tools/cmd/signature-fuzzer/internal/fuzz-generator"
)
const pkName = "fzTest"
// Basic options
var verbflag = flag.Int("v", 0, "Verbose trace output level")
var loopitflag = flag.Int("numit", 10, "Number of main loop iterations to run")
var seedflag = flag.Int64("seed", -1, "Random seed")
var execflag = flag.Bool("execdriver", false, "Exec fuzz-driver binary instead of invoking generator directly")
var numpkgsflag = flag.Int("numpkgs", 50, "Number of test packages")
var numfcnsflag = flag.Int("numfcns", 20, "Number of test functions per package.")
// Debugging/testing options. These tell the generator to emit "bad" code so as to
// test the logic for detecting errors and/or minimization.
var emitbadflag = flag.Int("emitbad", -1, "[Testing only] force generator to emit 'bad' code.")
var selbadpkgflag = flag.Int("badpkgidx", 0, "[Testing only] select index of bad package (used with -emitbad)")
var selbadfcnflag = flag.Int("badfcnidx", 0, "[Testing only] select index of bad function (used with -emitbad)")
var forcetmpcleanflag = flag.Bool("forcetmpclean", false, "[Testing only] force cleanup of temp dir")
var cleancacheflag = flag.Bool("cleancache", true, "[Testing only] don't clean the go cache")
var raceflag = flag.Bool("race", false, "[Testing only] build generated code with -race")
func verb(vlevel int, s string, a ...any) {
if *verbflag >= vlevel {
fmt.Printf(s, a...)
fmt.Printf("\n")
}
}
func warn(s string, a ...any) {
fmt.Fprintf(os.Stderr, s, a...)
fmt.Fprintf(os.Stderr, "\n")
}
func fatal(s string, a ...any) {
fmt.Fprintf(os.Stderr, s, a...)
fmt.Fprintf(os.Stderr, "\n")
os.Exit(1)
}
type config struct {
generator.GenConfig
tmpdir string
gendir string
buildOutFile string
runOutFile string
gcflags string
nerrors int
}
func usage(msg string) {
if len(msg) > 0 {
fmt.Fprintf(os.Stderr, "error: %s\n", msg)
}
fmt.Fprintf(os.Stderr, "usage: fuzz-runner [flags]\n\n")
flag.PrintDefaults()
fmt.Fprintf(os.Stderr, "Example:\n\n")
fmt.Fprintf(os.Stderr, " fuzz-runner -numit=500 -numpkgs=11 -numfcns=13 -seed=10101\n\n")
fmt.Fprintf(os.Stderr, " \tRuns 500 rounds of test case generation\n")
fmt.Fprintf(os.Stderr, " \tusing random see 10101, in each round emitting\n")
fmt.Fprintf(os.Stderr, " \t11 packages each with 13 function pairs.\n")
os.Exit(2)
}
// docmd executes the specified command in the dir given and pipes the
// output to stderr. return status is 0 if command passed, 1
// otherwise.
func docmd(cmd []string, dir string) int {
verb(2, "docmd: %s", strings.Join(cmd, " "))
c := exec.Command(cmd[0], cmd[1:]...)
if dir != "" {
c.Dir = dir
}
b, err := c.CombinedOutput()
st := 0
if err != nil {
warn("error executing cmd %s: %v",
strings.Join(cmd, " "), err)
st = 1
}
os.Stderr.Write(b)
return st
}
// docmdout forks and execs command 'cmd' in dir 'dir', redirecting
// stderr and stdout from the execution to file 'outfile'.
func docmdout(cmd []string, dir string, outfile string) int {
of, err := os.OpenFile(outfile, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644)
if err != nil {
fatal("opening outputfile %s: %v", outfile, err)
}
c := exec.Command(cmd[0], cmd[1:]...)
defer of.Close()
if dir != "" {
verb(2, "setting cmd.Dir to %s", dir)
c.Dir = dir
}
verb(2, "docmdout: %s > %s", strings.Join(cmd, " "), outfile)
c.Stdout = of
c.Stderr = of
err = c.Run()
st := 0
if err != nil {
warn("error executing cmd %s: %v",
strings.Join(cmd, " "), err)
st = 1
}
return st
}
// gen is the main hook for kicking off code generation. For
// non-minimization runs, 'singlepk' and 'singlefn' will both be -1
// (indicating that we want all functions and packages to be
// generated). If 'singlepk' is set to a non-negative value, then
// code generation will be restricted to the single package with that
// index (as a try at minimization), similarly with 'singlefn'
// restricting the codegen to a single specified function.
func (c *config) gen(singlepk int, singlefn int) {
// clean the output dir
verb(2, "cleaning outdir %s", c.gendir)
if err := os.RemoveAll(c.gendir); err != nil {
fatal("error cleaning gen dir %s: %v", c.gendir, err)
}
// emit code into the output dir. Here we either invoke the
// generator directly, or invoke fuzz-driver if -execflag is
// set. If the code generation process itself fails, this is
// typically a bug in the fuzzer itself, so it gets reported
// as a fatal error.
if *execflag {
args := []string{"fuzz-driver",
"-numpkgs", strconv.Itoa(c.NumTestPackages),
"-numfcns", strconv.Itoa(c.NumTestFunctions),
"-seed", strconv.Itoa(int(c.Seed)),
"-outdir", c.OutDir,
"-pkgpath", pkName,
"-maxfail", strconv.Itoa(c.MaxFail)}
if singlepk != -1 {
args = append(args, "-pkgmask", strconv.Itoa(singlepk))
}
if singlefn != -1 {
args = append(args, "-fcnmask", strconv.Itoa(singlefn))
}
if *emitbadflag != 0 {
args = append(args, "-emitbad", strconv.Itoa(*emitbadflag),
"-badpkgidx", strconv.Itoa(*selbadpkgflag),
"-badfcnidx", strconv.Itoa(*selbadfcnflag))
}
verb(1, "invoking fuzz-driver with args: %v", args)
st := docmd(args, "")
if st != 0 {
fatal("fatal error: generation failed, cmd was: %v", args)
}
} else {
if singlepk != -1 {
c.PkgMask = map[int]int{singlepk: 1}
}
if singlefn != -1 {
c.FcnMask = map[int]int{singlefn: 1}
}
verb(1, "invoking generator.Generate with config: %v", c.GenConfig)
errs := generator.Generate(c.GenConfig)
if errs != 0 {
log.Fatal("errors during generation")
}
}
}
// action performs a selected action/command in the generated code dir.
func (c *config) action(cmd []string, outfile string, emitout bool) int {
st := docmdout(cmd, c.gendir, outfile)
if emitout {
content, err := os.ReadFile(outfile)
if err != nil {
log.Fatal(err)
}
fmt.Fprintf(os.Stderr, "%s", content)
}
return st
}
func binaryName() string {
if runtime.GOOS == "windows" {
return pkName + ".exe"
} else {
return "./" + pkName
}
}
// build builds a generated corpus of Go code. If 'emitout' is set, then dump out the
// results of the build after it completes (during minimization emitout is set to false,
// since there is no need to see repeated errors).
func (c *config) build(emitout bool) int {
// Issue a build of the generated code.
c.buildOutFile = filepath.Join(c.tmpdir, "build.err.txt")
cmd := []string{"go", "build", "-o", binaryName()}
if c.gcflags != "" {
cmd = append(cmd, "-gcflags=all="+c.gcflags)
}
if *raceflag {
cmd = append(cmd, "-race")
}
cmd = append(cmd, ".")
verb(1, "build command is: %v", cmd)
return c.action(cmd, c.buildOutFile, emitout)
}
// run invokes a binary built from a generated corpus of Go code. If
// 'emitout' is set, then dump out the results of the run after it
// completes.
func (c *config) run(emitout bool) int {
// Issue a run of the generated code.
c.runOutFile = filepath.Join(c.tmpdir, "run.err.txt")
cmd := []string{filepath.Join(c.gendir, binaryName())}
verb(1, "run command is: %v", cmd)
return c.action(cmd, c.runOutFile, emitout)
}
type minimizeMode int
const (
minimizeBuildFailure = iota
minimizeRuntimeFailure
)
// minimize tries to minimize a failing scenario down to a single
// package and/or function if possible. This is done using an
// iterative search. Here 'minimizeMode' tells us whether we're
// looking for a compile-time error or a runtime error.
func (c *config) minimize(mode minimizeMode) int {
verb(0, "... starting minimization for failed directory %s", c.gendir)
foundPkg := -1
foundFcn := -1
// Locate bad package. Uses brute-force linear search, could do better...
for pidx := 0; pidx < c.NumTestPackages; pidx++ {
verb(1, "minimization: trying package %d", pidx)
c.gen(pidx, -1)
st := c.build(false)
if mode == minimizeBuildFailure {
if st != 0 {
// Found.
foundPkg = pidx
c.nerrors++
break
}
} else {
if st != 0 {
warn("run minimization: unexpected build failed while searching for bad pkg")
return 1
}
st := c.run(false)
if st != 0 {
// Found.
c.nerrors++
verb(1, "run minimization found bad package: %d", pidx)
foundPkg = pidx
break
}
}
}
if foundPkg == -1 {
verb(0, "** minimization failed, could not locate bad package")
return 1
}
warn("package minimization succeeded: found bad pkg %d", foundPkg)
// clean unused packages
for pidx := 0; pidx < c.NumTestPackages; pidx++ {
if pidx != foundPkg {
chp := filepath.Join(c.gendir, fmt.Sprintf("%s%s%d", c.Tag, generator.CheckerName, pidx))
if err := os.RemoveAll(chp); err != nil {
fatal("failed to clean pkg subdir %s: %v", chp, err)
}
clp := filepath.Join(c.gendir, fmt.Sprintf("%s%s%d", c.Tag, generator.CallerName, pidx))
if err := os.RemoveAll(clp); err != nil {
fatal("failed to clean pkg subdir %s: %v", clp, err)
}
}
}
// Locate bad function. Again, brute force.
for fidx := 0; fidx < c.NumTestFunctions; fidx++ {
c.gen(foundPkg, fidx)
st := c.build(false)
if mode == minimizeBuildFailure {
if st != 0 {
// Found.
verb(1, "build minimization found bad function: %d", fidx)
foundFcn = fidx
break
}
} else {
if st != 0 {
warn("run minimization: unexpected build failed while searching for bad fcn")
return 1
}
st := c.run(false)
if st != 0 {
// Found.
verb(1, "run minimization found bad function: %d", fidx)
foundFcn = fidx
break
}
}
// not the function we want ... continue the hunt
}
if foundFcn == -1 {
verb(0, "** function minimization failed, could not locate bad function")
return 1
}
warn("function minimization succeeded: found bad fcn %d", foundFcn)
return 0
}
// cleanTemp removes the temp dir we've been working with.
func (c *config) cleanTemp() {
if !*forcetmpcleanflag {
if c.nerrors != 0 {
verb(1, "preserving temp dir %s", c.tmpdir)
return
}
}
verb(1, "cleaning temp dir %s", c.tmpdir)
os.RemoveAll(c.tmpdir)
}
// perform is the top level driver routine for the program, containing the
// main loop. Each iteration of the loop performs a generate/build/run
// sequence, and then updates the seed afterwards if no failure is found.
// If a failure is detected, we try to minimize it and then return without
// attempting any additional tests.
func (c *config) perform() int {
defer c.cleanTemp()
// Main loop
for iter := 0; iter < *loopitflag; iter++ {
if iter != 0 && iter%50 == 0 {
// Note: cleaning the Go cache periodically is
// pretty much a requirement if you want to do
// things like overnight runs of the fuzzer,
// but it is also a very unfriendly thing do
// to if we're executing as part of a unit
// test run (in which case there may be other
// tests running in parallel with this
// one). Check the "cleancache" flag before
// doing this.
if *cleancacheflag {
docmd([]string{"go", "clean", "-cache"}, "")
}
}
verb(0, "... begin iteration %d with current seed %d", iter, c.Seed)
c.gen(-1, -1)
st := c.build(true)
if st != 0 {
c.minimize(minimizeBuildFailure)
return 1
}
st = c.run(true)
if st != 0 {
c.minimize(minimizeRuntimeFailure)
return 1
}
// update seed so that we get different code on the next iter.
c.Seed += 101
}
return 0
}
func main() {
log.SetFlags(0)
log.SetPrefix("fuzz-runner: ")
flag.Parse()
if flag.NArg() != 0 {
usage("unknown extra arguments")
}
verb(1, "in main, verblevel=%d", *verbflag)
tmpdir, err := os.MkdirTemp("", "fuzzrun")
if err != nil {
fatal("creation of tempdir failed: %v", err)
}
gendir := filepath.Join(tmpdir, "fuzzTest")
// select starting seed
if *seedflag == -1 {
now := time.Now()
*seedflag = now.UnixNano() % 123456789
}
// set up params for this run
c := &config{
GenConfig: generator.GenConfig{
NumTestPackages: *numpkgsflag, // 100
NumTestFunctions: *numfcnsflag, // 20
Seed: *seedflag,
OutDir: gendir,
Pragma: "-maxfail=9999",
PkgPath: pkName,
EmitBad: *emitbadflag,
BadPackageIdx: *selbadpkgflag,
BadFuncIdx: *selbadfcnflag,
},
tmpdir: tmpdir,
gendir: gendir,
}
// kick off the main loop.
st := c.perform()
// done
verb(1, "leaving main, num errors=%d", c.nerrors)
os.Exit(st)
}
================================================
FILE: cmd/signature-fuzzer/fuzz-runner/testdata/himom.go
================================================
// Copyright 2021 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package main
func main() {
println("hi mom!")
}
================================================
FILE: cmd/signature-fuzzer/internal/fuzz-generator/arrayparm.go
================================================
// Copyright 2021 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package generator
import (
"bytes"
"fmt"
)
// arrayparm describes a parameter of array type; it implements the
// "parm" interface.
type arrayparm struct {
aname string
qname string
nelements uint8
eltype parm
slice bool
isBlank
addrTakenHow
isGenValFunc
skipCompare
}
func (p arrayparm) IsControl() bool {
return false
}
func (p arrayparm) TypeName() string {
return p.aname
}
func (p arrayparm) QualName() string {
return p.qname
}
func (p arrayparm) Declare(b *bytes.Buffer, prefix string, suffix string, caller bool) {
n := p.aname
if caller {
n = p.qname
}
b.WriteString(fmt.Sprintf("%s %s%s", prefix, n, suffix))
}
func (p arrayparm) String() string {
return fmt.Sprintf("%s %d-element array of %s", p.aname, p.nelements, p.eltype.String())
}
func (p arrayparm) GenValue(s *genstate, f *funcdef, value int, caller bool) (string, int) {
var buf bytes.Buffer
verb(5, "arrayparm.GenValue(%d)", value)
n := p.aname
if caller {
n = p.qname
}
buf.WriteString(fmt.Sprintf("%s{", n))
for i := 0; i < int(p.nelements); i++ {
var valstr string
valstr, value = s.GenValue(f, p.eltype, value, caller)
writeCom(&buf, i)
buf.WriteString(valstr)
}
buf.WriteString("}")
return buf.String(), value
}
func (p arrayparm) GenElemRef(elidx int, path string) (string, parm) {
ene := p.eltype.NumElements()
verb(4, "begin GenElemRef(%d,%s) on %s ene %d", elidx, path, p.String(), ene)
// For empty arrays, convention is to return empty string
if ene == 0 {
return "", &p
}
// Find slot within array of element of interest
slot := elidx / ene
// If this is the element we're interested in, return it
if ene == 1 {
verb(4, "hit scalar element")
epath := fmt.Sprintf("%s[%d]", path, slot)
if path == "_" || p.IsBlank() {
epath = "_"
}
return epath, p.eltype
}
verb(4, "recur slot=%d GenElemRef(%d,...)", slot, elidx-(slot*ene))
// Otherwise our victim is somewhere inside the slot
ppath := fmt.Sprintf("%s[%d]", path, slot)
if p.IsBlank() {
ppath = "_"
}
return p.eltype.GenElemRef(elidx-(slot*ene), ppath)
}
func (p arrayparm) NumElements() int {
return p.eltype.NumElements() * int(p.nelements)
}
func (p arrayparm) HasPointer() bool {
return p.eltype.HasPointer() || p.slice
}
================================================
FILE: cmd/signature-fuzzer/internal/fuzz-generator/gen_test.go
================================================
// Copyright 2021 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package generator
import (
"bytes"
"os"
"os/exec"
"path/filepath"
"runtime"
"testing"
"golang.org/x/tools/internal/testenv"
)
func mkGenState() *genstate {
return &genstate{
GenConfig: GenConfig{
Tag: "gen",
OutDir: "/tmp",
NumTestPackages: 1,
NumTestFunctions: 10,
},
ipref: "foo/",
derefFuncs: make(map[string]string),
assignFuncs: make(map[string]string),
allocFuncs: make(map[string]string),
globVars: make(map[string]string),
}
}
func TestBasic(t *testing.T) {
checkTunables(tunables)
s := mkGenState()
for i := range 1000 {
s.wr = NewWrapRand(int64(i), RandCtlChecks|RandCtlPanic)
fp := s.GenFunc(i, i)
var buf bytes.Buffer
var b *bytes.Buffer = &buf
wr := NewWrapRand(int64(i), RandCtlChecks|RandCtlPanic)
s.wr = wr
s.emitCaller(fp, b, i)
s.wr = NewWrapRand(int64(i), RandCtlChecks|RandCtlPanic)
s.emitChecker(fp, b, i, true)
wr.Check(s.wr)
}
if s.errs != 0 {
t.Errorf("%d errors during Generate", s.errs)
}
}
func TestMoreComplicated(t *testing.T) {
saveit := tunables
defer func() { tunables = saveit }()
checkTunables(tunables)
s := mkGenState()
for i := range 10000 {
s.wr = NewWrapRand(int64(i), RandCtlChecks|RandCtlPanic)
fp := s.GenFunc(i, i)
var buf bytes.Buffer
var b *bytes.Buffer = &buf
wr := NewWrapRand(int64(i), RandCtlChecks|RandCtlPanic)
s.wr = wr
s.emitCaller(fp, b, i)
verb(1, "finished iter %d caller", i)
s.wr = NewWrapRand(int64(i), RandCtlChecks|RandCtlPanic)
s.emitChecker(fp, b, i, true)
verb(1, "finished iter %d checker", i)
wr.Check(s.wr)
if s.errs != 0 {
t.Errorf("%d errors during Generate iter %d", s.errs, i)
}
}
}
func TestIsBuildable(t *testing.T) {
testenv.NeedsTool(t, "go")
if runtime.GOOS == "android" {
t.Skipf("the dependencies are not available on android")
}
td := t.TempDir()
verb(1, "generating into temp dir %s", td)
checkTunables(tunables)
pack := filepath.Base(td)
s := GenConfig{
Tag: "x",
OutDir: td,
PkgPath: pack,
NumTestFunctions: 10,
NumTestPackages: 10,
MaxFail: 10,
RandCtl: RandCtlChecks | RandCtlPanic,
}
errs := Generate(s)
if errs != 0 {
t.Errorf("%d errors during Generate", errs)
}
verb(1, "building %s\n", td)
cmd := exec.Command("go", "run", ".")
cmd.Dir = td
coutput, cerr := cmd.CombinedOutput()
if cerr != nil {
t.Errorf("go build command failed: %s\n", string(coutput))
}
verb(1, "output is: %s\n", string(coutput))
}
// TestExhaustive does a series of code generation runs, starting with
// (relatively) simple code and then getting progressively more
// complex (more params, deeper structs, turning on additional
// features such as address-taken vars and reflect testing). The
// intent here is mainly to insure that the tester still works if you
// turn things on and off, e.g. that each feature is separately
// controllable and not linked to other things.
func TestExhaustive(t *testing.T) {
testenv.NeedsTool(t, "go")
if runtime.GOOS == "android" {
t.Skipf("the dependencies are not available on android")
}
if testing.Short() {
t.Skip("skipping test in short mode.")
}
td := t.TempDir()
verb(1, "generating into temp dir %s", td)
scenarios := []struct {
name string
adjuster func()
}{
{
"minimal",
func() {
tunables.nParmRange = 3
tunables.nReturnRange = 3
tunables.structDepth = 1
tunables.recurPerc = 0
tunables.methodPerc = 0
tunables.doReflectCall = false
tunables.doDefer = false
tunables.takeAddress = false
tunables.doFuncCallValues = false
tunables.doSkipCompare = false
checkTunables(tunables)
},
},
{
"moreparms",
func() {
tunables.nParmRange = 15
tunables.nReturnRange = 7
tunables.structDepth = 3
checkTunables(tunables)
},
},
{
"addrecur",
func() {
tunables.recurPerc = 20
checkTunables(tunables)
},
},
{
"addmethod",
func() {
tunables.methodPerc = 25
tunables.pointerMethodCallPerc = 30
checkTunables(tunables)
},
},
{
"addtakeaddr",
func() {
tunables.takeAddress = true
tunables.takenFraction = 20
checkTunables(tunables)
},
},
{
"addreflect",
func() {
tunables.doReflectCall = true
checkTunables(tunables)
},
},
{
"adddefer",
func() {
tunables.doDefer = true
checkTunables(tunables)
},
},
{
"addfuncval",
func() {
tunables.doFuncCallValues = true
checkTunables(tunables)
},
},
{
"addfuncval",
func() {
tunables.doSkipCompare = true
checkTunables(tunables)
},
},
}
// Loop over scenarios and make sure each one works properly.
for i, s := range scenarios {
t.Logf("running %s\n", s.name)
s.adjuster()
os.RemoveAll(td)
pack := filepath.Base(td)
c := GenConfig{
Tag: "x",
OutDir: td,
PkgPath: pack,
NumTestFunctions: 10,
NumTestPackages: 10,
Seed: int64(i + 9),
MaxFail: 10,
RandCtl: RandCtlChecks | RandCtlPanic,
}
errs := Generate(c)
if errs != 0 {
t.Errorf("%d errors during scenarios %q Generate", errs, s.name)
}
cmd := exec.Command("go", "run", ".")
cmd.Dir = td
coutput, cerr := cmd.CombinedOutput()
if cerr != nil {
t.Fatalf("run failed for scenario %q: %s\n", s.name, string(coutput))
}
verb(1, "output is: %s\n", string(coutput))
}
}
func TestEmitBadBuildFailure(t *testing.T) {
testenv.NeedsTool(t, "go")
if runtime.GOOS == "android" {
t.Skipf("the dependencies are not available on android")
}
td := t.TempDir()
verb(1, "generating into temp dir %s", td)
checkTunables(tunables)
pack := filepath.Base(td)
s := GenConfig{
Tag: "x",
OutDir: td,
PkgPath: pack,
NumTestFunctions: 10,
NumTestPackages: 10,
MaxFail: 10,
RandCtl: RandCtlChecks | RandCtlPanic,
EmitBad: 1,
}
errs := Generate(s)
if errs != 0 {
t.Errorf("%d errors during Generate", errs)
}
cmd := exec.Command("go", "build", ".")
cmd.Dir = td
coutput, cerr := cmd.CombinedOutput()
if cerr == nil {
t.Errorf("go build command passed, expected failure. output: %s\n", string(coutput))
}
}
func TestEmitBadRunFailure(t *testing.T) {
testenv.NeedsTool(t, "go")
if runtime.GOOS == "android" {
t.Skipf("the dependencies are not available on android")
}
td := t.TempDir()
verb(1, "generating into temp dir %s", td)
checkTunables(tunables)
pack := filepath.Base(td)
s := GenConfig{
Tag: "x",
OutDir: td,
PkgPath: pack,
NumTestFunctions: 10,
NumTestPackages: 10,
MaxFail: 10,
RandCtl: RandCtlChecks | RandCtlPanic,
EmitBad: 2,
}
errs := Generate(s)
if errs != 0 {
t.Errorf("%d errors during Generate", errs)
}
// build
cmd := exec.Command("go", "build", ".")
cmd.Dir = td
coutput, cerr := cmd.CombinedOutput()
if cerr != nil {
t.Fatalf("build failed: %s\n", string(coutput))
}
// run
cmd = exec.Command("./" + pack)
cmd.Dir = td
coutput, cerr = cmd.CombinedOutput()
if cerr == nil {
t.Fatalf("run passed, expected failure -- run output: %s", string(coutput))
}
}
================================================
FILE: cmd/signature-fuzzer/internal/fuzz-generator/generator.go
================================================
// Copyright 2021 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// This package generates source code for a stand-alone Go program
// useful for function signature fuzzing. The generated program is a
// series of function pairs, a "Caller" function and a "Checker"
// function. The signature of the Checker function is generated
// randomly (random number of parameters and returns, each with
// randomly chosen types). The "Caller" func contains invocations of
// the "Checker" function, each passing randomly chosen values to the
// params of the "Checker", then the caller verifies that expected
// values are returned correctly. The "Checker" function in turn has
// code to verify that the expected values arrive correctly, and so
// on.
//
// The main exported items of interest for this package are:
//
// - the Generate function, which takes a GenConfig object and emits
// code according to the config's specification
//
// - the GenConfig struct, which is basically a large collection of
// knobs/switches to control the mechanics of how/where code is
// generated
//
// - the TunableParams struct, which controls the nature of the
// generated code (for example, the maximum number of function
// parameters, etc), and the SetTunables func which tells the
// package what tunable parameters to use.
// Notes for posterity:
// - many parts of this package would have been better off being written
// using text/template instead of generating code directly; perhaps
// at some point it could be converted over (big job).
// - for the various 'fractions' fields in the TunableParams struct,
// it would be good to have a named type of some sort, with methods
// for managing things like checking to make sure values sum to 100.
package generator
import (
"bytes"
"crypto/sha1"
"errors"
"fmt"
"html/template"
"log"
"os"
"os/exec"
"path/filepath"
"slices"
"strconv"
"strings"
)
// GenConfig contains configuration parameters relating to the
// mechanics of the code generation, e.g. how many packages/functions
// to emit, path to a directory into which we place the generated
// code, prefixes/packagenames for the generate code, and so on.
type GenConfig struct {
// Tag is a string prefix prepended to functions within
// the generated code.
Tag string
// Output directory in to which we'll emit generated code.
// This will be created if it does not exist.
OutDir string
// Packagepath prefix given to the generated code.
PkgPath string
// Number of test packages created within the generated corpus.
// Each test package is essentially an independent collection
// generated code; the point of having multiple packages is to
// be able to get faster builds (more parallelism), and to avoid
// the compile time issues that crop up with 'giant' packages.
NumTestPackages int
// Number of test function pairs within each generated test package.
// Each pair consists of a "caller" function and "callee" function.
NumTestFunctions int
// Seed for random number generator.
Seed int64
// Pragma is a "// go:..." compiler directive to apply to the
// callee function as part of a generated function pair.
Pragma string
// Function and package mask used for minimization purposes.
// If a given mask is non-nil, then the generator will only
// emit code for a given func or package if its index is
// present in the mask map.
FcnMask map[int]int
PkgMask map[int]int
// Maximum number of failures to encounter before bailing out.
MaxFail int
// forcestackgrowth if set tells the generator to insert
// calls to runtime.gcTestMoveStackOnNextCall at various points
// in the generated code.
ForceStackGrowth bool
// Random number generator control flag (debugging)
RandCtl int
// Tells the generator to run "goimports" on the emitted code.
RunGoImports bool
// Debugging/testing hook. If set to 1, emit code that will cause the
// build to fail; if set to 2, emit code that will cause a test to fail.
EmitBad int
// If EmitBad above is set, then these can be used to select the ID of
// a specific bad func/package.
BadPackageIdx int
BadFuncIdx int
}
const CallerName = "Caller"
const CheckerName = "Checker"
// TunableParams contains configuration parameters that control the
// flavor of code generated for a given test function. This includes
// things like the number of params/returns, the percentages of types
// (int, struct, etc) of the params/returns, and so on.
type TunableParams struct {
// between 0 and N params
nParmRange uint8
// between 0 and N returns
nReturnRange uint8
// structs have between 0 and N members
nStructFields uint8
// arrays/slices have between 0 and N elements
nArrayElements uint8
// fraction of slices vs arrays. This is a value between 0 and 100 (0 meaning
// no slices [only arrays] and 100 meaning all slices, no arrays).
sliceFraction uint8
// Controls how often "int" vars wind up as 8/16/32/64, should
// add up to 100. Ex: 100 0 0 0 means all ints are 8 bit, 25
// 25 25 25 means equal likelihood of all types.
intBitRanges [4]uint8
// Similar to the above but for 32/64 float types
floatBitRanges [2]uint8
// Similar to the above but for unsigned, signed ints.
unsignedRanges [2]uint8
// Percentage of params, struct fields that should be "_". Ranges
// from 0 to 100.
blankPerc uint8
// How deeply structs are allowed to be nested (ranges from 0 to N).
structDepth uint8
// Fraction of param and return types assigned to each of:
// struct/array/map/pointer/int/float/complex/byte/string at the
// top level. If nesting precludes using a struct, other types
// are chosen from instead according to same proportions. The sum
// of typeFractions values should add up to 100.
typeFractions [9]uint8
// Percentage of the time we'll emit recursive calls, from 0 to 100.
recurPerc uint8
// Percentage of time that we turn the test function into a method,
// and if it is a method, fraction of time that we use a pointer
// method call vs value method call. Each range from 0 to 100.
methodPerc uint8
pointerMethodCallPerc uint8
// If true, test reflect.Call path as well.
doReflectCall bool
// If true, then randomly take addresses of params/returns.
takeAddress bool
// Fraction of the time that any params/returns are address taken.
// Ranges from 0 to 100.
takenFraction uint8
// For a given address-taken param or return, controls the
// manner in which the indirect read or write takes
// place. This is a set of percentages for
// not/simple/passed/heap, where "not" means not address
// taken, "simple" means a simple read or write, "passed"
// means that the address is passed to a well-behaved
// function, and "heap" means that the address is assigned to
// a global. Values in addrFractions should add up to 100.
addrFractions [4]uint8
// If true, then perform testing of go/defer statements.
doDefer bool
// fraction of test functions for which we emit a defer. Ranges from 0 to 100.
deferFraction uint8
// If true, randomly pick between emitting a value by literal
// (e.g. "int(1)" vs emitting a call to a function that
// will produce the same value (e.g. "myHelperEmitsInt1()").
doFuncCallValues bool
// Fraction of the time that we emit a function call to create
// a param value vs emitting a literal. Ranges from 0 to 100.
funcCallValFraction uint8
// If true, randomly decide to not check selected components of
// a composite value (e.g. for a struct, check field F1 but not F2).
// The intent is to generate partially live values.
doSkipCompare bool
// Fraction of the time that we decided to skip sub-components of
// composite values. Ranges from 0 to 100.
skipCompareFraction uint8
}
// SetTunables accepts a TunableParams object, checks to make sure
// that the settings in it are sane/logical, and applies the
// parameters for any subsequent calls to the Generate function. This
// function will issue a fatal error if any of the tunable params are
// incorrect/insane (for example, a 'percentage' value outside the
// range of 0-100).
func SetTunables(t TunableParams) {
checkTunables(t)
tunables = t
}
var defaultTypeFractions = [9]uint8{
10, // struct
10, // array
10, // map
15, // pointer
20, // numeric
15, // float
5, // complex
5, // byte
10, // string
}
const (
// Param not address taken.
StructTfIdx = iota
ArrayTfIdx
MapTfIdx
PointerTfIdx
NumericTfIdx
FloatTfIdx
ComplexTfIdx
ByteTfIdx
StringTfIdx
)
var tunables = TunableParams{
nParmRange: 15,
nReturnRange: 7,
nStructFields: 7,
nArrayElements: 5,
sliceFraction: 50,
intBitRanges: [4]uint8{30, 20, 20, 30},
floatBitRanges: [2]uint8{50, 50},
unsignedRanges: [2]uint8{50, 50},
blankPerc: 15,
structDepth: 3,
typeFractions: defaultTypeFractions,
recurPerc: 20,
methodPerc: 10,
pointerMethodCallPerc: 50,
doReflectCall: true,
doDefer: true,
takeAddress: true,
doFuncCallValues: true,
takenFraction: 20,
deferFraction: 30,
funcCallValFraction: 5,
doSkipCompare: true,
skipCompareFraction: 10,
addrFractions: [4]uint8{50, 25, 15, 10},
}
func DefaultTunables() TunableParams {
return tunables
}
func checkTunables(t TunableParams) {
var s int = 0
for _, v := range t.intBitRanges {
s += int(v)
}
if s != 100 {
log.Fatal(errors.New("intBitRanges tunable does not sum to 100"))
}
s = 0
for _, v := range t.unsignedRanges {
s += int(v)
}
if s != 100 {
log.Fatal(errors.New("unsignedRanges tunable does not sum to 100"))
}
if t.blankPerc > 100 {
log.Fatal(errors.New("blankPerc bad value, over 100"))
}
if t.recurPerc > 100 {
log.Fatal(errors.New("recurPerc bad value, over 100"))
}
if t.methodPerc > 100 {
log.Fatal(errors.New("methodPerc bad value, over 100"))
}
if t.pointerMethodCallPerc > 100 {
log.Fatal(errors.New("pointerMethodCallPerc bad value, over 100"))
}
s = 0
for _, v := range t.floatBitRanges {
s += int(v)
}
if s != 100 {
log.Fatal(errors.New("floatBitRanges tunable does not sum to 100"))
}
s = 0
for _, v := range t.typeFractions {
s += int(v)
}
if s != 100 {
panic(errors.New("typeFractions tunable does not sum to 100"))
}
s = 0
for _, v := range t.addrFractions {
s += int(v)
}
if s != 100 {
log.Fatal(errors.New("addrFractions tunable does not sum to 100"))
}
if t.takenFraction > 100 {
log.Fatal(errors.New("takenFraction not between 0 and 100"))
}
if t.deferFraction > 100 {
log.Fatal(errors.New("deferFraction not between 0 and 100"))
}
if t.sliceFraction > 100 {
log.Fatal(errors.New("sliceFraction not between 0 and 100"))
}
if t.skipCompareFraction > 100 {
log.Fatal(errors.New("skipCompareFraction not between 0 and 100"))
}
}
func (t *TunableParams) DisableReflectionCalls() {
t.doReflectCall = false
}
func (t *TunableParams) DisableRecursiveCalls() {
t.recurPerc = 0
}
func (t *TunableParams) DisableMethodCalls() {
t.methodPerc = 0
}
func (t *TunableParams) DisableTakeAddr() {
t.takeAddress = false
}
func (t *TunableParams) DisableDefer() {
t.doDefer = false
}
func (t *TunableParams) LimitInputs(n int) error {
if n > 100 {
return fmt.Errorf("value %d passed to LimitInputs is too large *(max 100)", n)
}
if n < 0 {
return fmt.Errorf("value %d passed to LimitInputs is invalid", n)
}
t.nParmRange = uint8(n)
return nil
}
func (t *TunableParams) LimitOutputs(n int) error {
if n > 100 {
return fmt.Errorf("value %d passed to LimitOutputs is too large *(max 100)", n)
}
if n < 0 {
return fmt.Errorf("value %d passed to LimitOutputs is invalid", n)
}
t.nReturnRange = uint8(n)
return nil
}
// ParseMaskString parses a string of the form K,J,...,M-N,Q-R,...,Z
// e.g. comma-separated integers or ranges of integers, returning the
// result in a form suitable for FcnMask or PkgMask fields in a
// Config. Here "tag" holds the mask flavor (fcn or pkg) and "arg" is
// the string argument to be parsed.
func ParseMaskString(arg string, tag string) (map[int]int, error) {
if arg == "" {
return nil, nil
}
verb(1, "%s mask is %s", tag, arg)
m := make(map[int]int)
for s := range strings.SplitSeq(arg, ":") {
if strings.Contains(s, "-") {
rng := strings.Split(s, "-")
if len(rng) != 2 {
return nil, fmt.Errorf("malformed range %s in %s mask arg", s, tag)
}
i, err := strconv.Atoi(rng[0])
if err != nil {
return nil, fmt.Errorf("malformed range value %s in %s mask arg", rng[0], tag)
}
j, err2 := strconv.Atoi(rng[1])
if err2 != nil {
return nil, fmt.Errorf("malformed range value %s in %s mask arg", rng[1], tag)
}
for k := i; k < j; k++ {
m[k] = 1
}
} else {
i, err := strconv.Atoi(s)
if err != nil {
return nil, fmt.Errorf("malformed value %s in %s mask arg", s, tag)
}
m[i] = 1
}
}
return m, nil
}
func writeCom(b *bytes.Buffer, i int) {
if i != 0 {
b.WriteString(", ")
}
}
var Verbctl int = 0
func verb(vlevel int, s string, a ...any) {
if Verbctl >= vlevel {
fmt.Printf(s, a...)
fmt.Printf("\n")
}
}
type funcdef struct {
idx int
structdefs []structparm
arraydefs []arrayparm
typedefs []typedefparm
mapdefs []mapparm
mapkeytypes []parm
mapkeytmps []string
mapkeyts string
receiver parm
params []parm
returns []parm
values []int
dodefc uint8
dodefp []uint8
rstack int
recur bool
isMethod bool
}
type genstate struct {
GenConfig
ipref string
//tag string
//numtpk int
pkidx int
errs int
//pragma string
//sforce bool
//randctl int
tunables TunableParams
tstack []TunableParams
derefFuncs map[string]string
newDerefFuncs []funcdesc
assignFuncs map[string]string
newAssignFuncs []funcdesc
allocFuncs map[string]string
newAllocFuncs []funcdesc
genvalFuncs map[string]string
newGenvalFuncs []funcdesc
globVars map[string]string
newGlobVars []funcdesc
wr *wraprand
}
func (s *genstate) intFlavor() string {
which := uint8(s.wr.Intn(100))
if which < s.tunables.unsignedRanges[0] {
return "uint"
}
return "int"
}
func (s *genstate) intBits() uint32 {
which := uint8(s.wr.Intn(100))
var t uint8 = 0
var bits uint32 = 8
for _, v := range s.tunables.intBitRanges {
t += v
if which < t {
return bits
}
bits *= 2
}
return uint32(s.tunables.intBitRanges[3])
}
func (s *genstate) floatBits() uint32 {
which := uint8(s.wr.Intn(100))
if which < s.tunables.floatBitRanges[0] {
return uint32(32)
}
return uint32(64)
}
func (s *genstate) genAddrTaken() addrTakenHow {
which := uint8(s.wr.Intn(100))
res := notAddrTaken
var t uint8 = 0
for _, v := range s.tunables.addrFractions {
t += v
if which < t {
return res
}
res++
}
return notAddrTaken
}
func (s *genstate) pushTunables() {
s.tstack = append(s.tstack, s.tunables)
}
func (s *genstate) popTunables() {
if len(s.tstack) == 0 {
panic("untables stack underflow")
}
s.tunables = s.tstack[0]
s.tstack = s.tstack[1:]
}
// redistributeFraction accepts a value 'toIncorporate' and updates
// 'typeFraction' to add in the values from 'toIncorporate' equally to
// all slots not in 'avoid'. This is done by successively walking
// through 'typeFraction' adding 1 to each non-avoid slot, then
// repeating until we've added a total of 'toIncorporate' elements.
// See precludeSelectedTypes below for more info.
func (s *genstate) redistributeFraction(toIncorporate uint8, avoid []int) {
inavoid := func(j int) bool {
return slices.Contains(avoid, j)
}
doredis := func() {
for {
for i := range s.tunables.typeFractions {
if inavoid(i) {
continue
}
s.tunables.typeFractions[i]++
toIncorporate--
if toIncorporate == 0 {
return
}
}
}
}
doredis()
checkTunables(s.tunables)
}
// precludeSelectedTypes accepts a set of values (t, t2, ...)
// corresponding to slots in 'typeFractions', sums up the values from
// the slots, zeroes out the slots, and finally takes the values and
// redistributes them equally to the other slots. For example,
// suppose 'typeFractions' starts as [10, 10, 10, 15, 20, 15, 5, 5, 10],
// then we decide we want to eliminate or 'knock out' map types and
// pointer types (slots 2 and 3 in the array above) going forward. To
// restore the invariant that values in 'typeFractions' sum to 100, we
// take the values from slots 2 and 3 (a total of 25) and evenly
// distribute those values to the other slots in the array.
func (s *genstate) precludeSelectedTypes(t int, t2 ...int) {
avoid := []int{t}
avoid = append(avoid, t2...)
f := uint8(0)
for _, idx := range avoid {
f += s.tunables.typeFractions[idx]
s.tunables.typeFractions[idx] = 0
}
s.redistributeFraction(f, avoid)
}
func (s *genstate) GenMapKeyType(f *funcdef, depth int, pidx int) parm {
s.pushTunables()
defer s.popTunables()
// maps we can't allow at all; pointers might be possible but
// would be too much work to arrange. Avoid slices as well.
s.tunables.sliceFraction = 0
s.precludeSelectedTypes(MapTfIdx, PointerTfIdx)
return s.GenParm(f, depth+1, false, pidx)
}
func (s *genstate) GenParm(f *funcdef, depth int, mkctl bool, pidx int) parm {
// Enforcement for struct/array/map/pointer array nesting depth.
toodeep := depth >= int(s.tunables.structDepth)
if toodeep {
s.pushTunables()
defer s.popTunables()
s.precludeSelectedTypes(StructTfIdx, ArrayTfIdx, MapTfIdx, PointerTfIdx)
}
// Convert tf into a cumulative sum
tf := s.tunables.typeFractions
sum := uint8(0)
for i := range len(tf) {
sum += tf[i]
tf[i] = sum
}
isblank := uint8(s.wr.Intn(100)) < s.tunables.blankPerc
addrTaken := notAddrTaken
if depth == 0 && tunables.takeAddress && !isblank {
addrTaken = s.genAddrTaken()
}
isGenValFunc := tunables.doFuncCallValues &&
uint8(s.wr.Intn(100)) < s.tunables.funcCallValFraction
// Make adjusted selection (pick a bucket within tf)
which := uint8(s.wr.Intn(100))
verb(3, "which=%d", which)
var retval parm
switch {
case which < tf[StructTfIdx]:
{
if toodeep {
panic("should not be here")
}
var sp structparm
ns := len(f.structdefs)
sp.sname = fmt.Sprintf("StructF%dS%d", f.idx, ns)
sp.qname = fmt.Sprintf("%s.StructF%dS%d",
s.checkerPkg(pidx), f.idx, ns)
f.structdefs = append(f.structdefs, sp)
tnf := int64(s.tunables.nStructFields) / int64(depth+1)
nf := int(s.wr.Intn(tnf))
for range nf {
fp := s.GenParm(f, depth+1, false, pidx)
skComp := tunables.doSkipCompare &&
uint8(s.wr.Intn(100)) < s.tunables.skipCompareFraction
if skComp && checkableElements(fp) != 0 {
fp.SetSkipCompare(SkipAll)
}
sp.fields = append(sp.fields, fp)
}
f.structdefs[ns] = sp
retval = &sp
}
case which < tf[ArrayTfIdx]:
{
if toodeep {
panic("should not be here")
}
var ap arrayparm
ns := len(f.arraydefs)
nel := uint8(s.wr.Intn(int64(s.tunables.nArrayElements)))
issl := uint8(s.wr.Intn(100)) < s.tunables.sliceFraction
ap.aname = fmt.Sprintf("ArrayF%dS%dE%d", f.idx, ns, nel)
ap.qname = fmt.Sprintf("%s.ArrayF%dS%dE%d", s.checkerPkg(pidx),
f.idx, ns, nel)
f.arraydefs = append(f.arraydefs, ap)
ap.nelements = nel
ap.slice = issl
ap.eltype = s.GenParm(f, depth+1, false, pidx)
ap.eltype.SetBlank(false)
skComp := tunables.doSkipCompare &&
uint8(s.wr.Intn(100)) < s.tunables.skipCompareFraction
if skComp && checkableElements(ap.eltype) != 0 {
if issl {
ap.SetSkipCompare(SkipPayload)
}
}
f.arraydefs[ns] = ap
retval = &ap
}
case which < tf[MapTfIdx]:
{
if toodeep {
panic("should not be here")
}
var mp mapparm
ns := len(f.mapdefs)
// append early, since calls below might also append
f.mapdefs = append(f.mapdefs, mp)
f.mapkeytmps = append(f.mapkeytmps, "")
f.mapkeytypes = append(f.mapkeytypes, mp.keytype)
mp.aname = fmt.Sprintf("MapF%dM%d", f.idx, ns)
if f.mapkeyts == "" {
f.mapkeyts = fmt.Sprintf("MapKeysF%d", f.idx)
}
mp.qname = fmt.Sprintf("%s.MapF%dM%d", s.checkerPkg(pidx),
f.idx, ns)
mkt := fmt.Sprintf("Mk%dt%d", f.idx, ns)
mp.keytmp = mkt
mk := s.GenMapKeyType(f, depth+1, pidx)
mp.keytype = mk
mp.valtype = s.GenParm(f, depth+1, false, pidx)
mp.valtype.SetBlank(false)
mp.keytype.SetBlank(false)
// now update the previously appended placeholders
f.mapdefs[ns] = mp
f.mapkeytypes[ns] = mk
f.mapkeytmps[ns] = mkt
retval = &mp
}
case which < tf[PointerTfIdx]:
{
if toodeep {
panic("should not be here")
}
pp := mkPointerParm(s.GenParm(f, depth+1, false, pidx))
retval = &pp
}
case which < tf[NumericTfIdx]:
{
var ip numparm
ip.tag = s.intFlavor()
ip.widthInBits = s.intBits()
if mkctl {
ip.ctl = true
}
retval = &ip
}
case which < tf[FloatTfIdx]:
{
var fp numparm
fp.tag = "float"
fp.widthInBits = s.floatBits()
retval = &fp
}
case which < tf[ComplexTfIdx]:
{
var fp numparm
fp.tag = "complex"
fp.widthInBits = s.floatBits() * 2
retval = &fp
}
case which < tf[ByteTfIdx]:
{
var bp numparm
bp.tag = "byte"
bp.widthInBits = 8
retval = &bp
}
case which < tf[StringTfIdx]:
{
var sp stringparm
sp.tag = "string"
skComp := tunables.doSkipCompare &&
uint8(s.wr.Intn(100)) < s.tunables.skipCompareFraction
if skComp {
sp.SetSkipCompare(SkipPayload)
}
retval = &sp
}
default:
{
// fallback
var ip numparm
ip.tag = "uint"
ip.widthInBits = 8
retval = &ip
}
}
if !mkctl {
retval.SetBlank(isblank)
}
retval.SetAddrTaken(addrTaken)
retval.SetIsGenVal(isGenValFunc)
return retval
}
func (s *genstate) GenReturn(f *funcdef, depth int, pidx int) parm {
return s.GenParm(f, depth, false, pidx)
}
// GenFunc cooks up the random signature (and other attributes) of a
// given checker function, returning a funcdef object that describes
// the new fcn.
func (s *genstate) GenFunc(fidx int, pidx int) *funcdef {
f := new(funcdef)
f.idx = fidx
numParams := int(s.wr.Intn(int64(1 + int(s.tunables.nParmRange))))
numReturns := int(s.wr.Intn(int64(1 + int(s.tunables.nReturnRange))))
f.recur = uint8(s.wr.Intn(100)) < s.tunables.recurPerc
f.isMethod = uint8(s.wr.Intn(100)) < s.tunables.methodPerc
genReceiverType := func() {
// Receiver type can't be pointer type. Temporarily update
// tunables to eliminate that possibility.
s.pushTunables()
defer s.popTunables()
s.precludeSelectedTypes(PointerTfIdx)
target := s.GenParm(f, 0, false, pidx)
target.SetBlank(false)
f.receiver = s.makeTypedefParm(f, target, pidx)
if f.receiver.IsBlank() {
f.recur = false
}
}
if f.isMethod {
genReceiverType()
}
needControl := f.recur
f.dodefc = uint8(s.wr.Intn(100))
pTaken := uint8(s.wr.Intn(100)) < s.tunables.takenFraction
for range numParams {
newparm := s.GenParm(f, 0, needControl, pidx)
if !pTaken {
newparm.SetAddrTaken(notAddrTaken)
}
if newparm.IsControl() {
needControl = false
}
f.params = append(f.params, newparm)
f.dodefp = append(f.dodefp, uint8(s.wr.Intn(100)))
}
if f.recur && needControl {
f.recur = false
}
rTaken := uint8(s.wr.Intn(100)) < s.tunables.takenFraction
for range numReturns {
r := s.GenReturn(f, 0, pidx)
if !rTaken {
r.SetAddrTaken(notAddrTaken)
}
f.returns = append(f.returns, r)
}
spw := uint(s.wr.Intn(11))
rstack := max(1< 0 {
b.WriteString(" := ")
}
pref := s.checkerPkg(pidx)
if f.isMethod {
pref = "rcvr"
}
b.WriteString(fmt.Sprintf("%s.Test%d(", pref, f.idx))
for pi := range f.params {
writeCom(b, pi)
b.WriteString(fmt.Sprintf("p%d", pi))
}
b.WriteString(")\n")
// check values returned (normal call case)
s.emitCheckReturnsInCaller(f, b, pidx, false /* not a reflect call */)
b.WriteString(" }") // end of 'if normal call' block
if s.tunables.doReflectCall {
b.WriteString("else {\n") // beginning of reflect call block
// now make the same call via reflection
b.WriteString(" // same call via reflection\n")
b.WriteString(fmt.Sprintf(" Mode[%d] = \"reflect\"\n", pidx))
if f.isMethod {
b.WriteString(" rcv := reflect.ValueOf(rcvr)\n")
b.WriteString(fmt.Sprintf(" rc := rcv.MethodByName(\"Test%d\")\n", f.idx))
} else {
b.WriteString(fmt.Sprintf(" rc := reflect.ValueOf(%s.Test%d)\n",
s.checkerPkg(pidx), f.idx))
}
b.WriteString(" ")
if len(f.returns) > 0 {
b.WriteString("rvslice := ")
}
b.WriteString(" rc.Call([]reflect.Value{")
for pi := range f.params {
writeCom(b, pi)
b.WriteString(fmt.Sprintf("reflect.ValueOf(p%d)", pi))
}
b.WriteString("})\n")
// check values returned (reflect call case)
s.emitCheckReturnsInCaller(f, b, pidx, true /* is a reflect call */)
b.WriteString("}\n") // end of reflect call block
}
b.WriteString(fmt.Sprintf("\n EndFcn(%d)\n", pidx))
b.WriteString("}\n\n")
}
func checkableElements(p parm) int {
if p.IsBlank() {
return 0
}
sp, isstruct := p.(*structparm)
if isstruct {
s := 0
for fi := range sp.fields {
s += checkableElements(sp.fields[fi])
}
return s
}
ap, isarray := p.(*arrayparm)
if isarray {
if ap.nelements == 0 {
return 0
}
return int(ap.nelements) * checkableElements(ap.eltype)
}
return 1
}
// funcdesc describes an auto-generated helper function or global
// variable, such as an allocation function (returns new(T)) or a
// pointer assignment function (assigns value of T to type *T). Here
// 'p' is a param type T, 'pp' is a pointer type *T, 'name' is the
// name within the generated code of the function or variable and
// 'tag' is a descriptive tag used to look up the entity in a map (so
// that we don't have to emit multiple copies of a function that
// assigns int to *int, for example).
type funcdesc struct {
p parm
pp parm
name string
tag string
payload string
}
func (s *genstate) emitDerefFuncs(b *bytes.Buffer, emit bool) {
b.WriteString("// dereference helpers\n")
for _, fd := range s.newDerefFuncs {
if !emit {
b.WriteString(fmt.Sprintf("\n// skip derefunc %s\n", fd.name))
delete(s.derefFuncs, fd.tag)
continue
}
b.WriteString("\n//go:noinline\n")
b.WriteString(fmt.Sprintf("func %s(", fd.name))
fd.pp.Declare(b, "x", "", false)
b.WriteString(") ")
fd.p.Declare(b, "", "", false)
b.WriteString(" {\n")
b.WriteString(" return *x\n")
b.WriteString("}\n")
}
s.newDerefFuncs = nil
}
func (s *genstate) emitAssignFuncs(b *bytes.Buffer, emit bool) {
b.WriteString("// assign helpers\n")
for _, fd := range s.newAssignFuncs {
if !emit {
b.WriteString(fmt.Sprintf("\n// skip assignfunc %s\n", fd.name))
delete(s.assignFuncs, fd.tag)
continue
}
b.WriteString("\n//go:noinline\n")
b.WriteString(fmt.Sprintf("func %s(", fd.name))
fd.pp.Declare(b, "x", "", false)
b.WriteString(", ")
fd.p.Declare(b, "v", "", false)
b.WriteString(") {\n")
b.WriteString(" *x = v\n")
b.WriteString("}\n")
}
s.newAssignFuncs = nil
}
func (s *genstate) emitNewFuncs(b *bytes.Buffer, emit bool) {
b.WriteString("// 'new' funcs\n")
for _, fd := range s.newAllocFuncs {
if !emit {
b.WriteString(fmt.Sprintf("\n// skip newfunc %s\n", fd.name))
delete(s.allocFuncs, fd.tag)
continue
}
b.WriteString("\n//go:noinline\n")
b.WriteString(fmt.Sprintf("func %s(", fd.name))
fd.p.Declare(b, "i", "", false)
b.WriteString(") ")
fd.pp.Declare(b, "", "", false)
b.WriteString(" {\n")
b.WriteString(" x := new(")
fd.p.Declare(b, "", "", false)
b.WriteString(")\n")
b.WriteString(" *x = i\n")
b.WriteString(" return x\n")
b.WriteString("}\n\n")
}
s.newAllocFuncs = nil
}
func (s *genstate) emitGlobalVars(b *bytes.Buffer, emit bool) {
b.WriteString("// global vars\n")
for _, fd := range s.newGlobVars {
if !emit {
b.WriteString(fmt.Sprintf("\n// skip gvar %s\n", fd.name))
delete(s.globVars, fd.tag)
continue
}
b.WriteString("var ")
fd.pp.Declare(b, fd.name, "", false)
b.WriteString("\n")
}
s.newGlobVars = nil
b.WriteString("\n")
}
func (s *genstate) emitGenValFuncs(f *funcdef, b *bytes.Buffer, emit bool) {
b.WriteString("// genval helpers\n")
for _, fd := range s.newGenvalFuncs {
if !emit {
b.WriteString(fmt.Sprintf("\n// skip genvalfunc %s\n", fd.name))
delete(s.genvalFuncs, fd.tag)
continue
}
b.WriteString("\n//go:noinline\n")
rcvr := ""
if f.mapkeyts != "" {
rcvr = fmt.Sprintf("(mkt *%s) ", f.mapkeyts)
}
b.WriteString(fmt.Sprintf("func %s%s() ", rcvr, fd.name))
fd.p.Declare(b, "", "", false)
b.WriteString(" {\n")
if f.mapkeyts != "" {
contained := containedParms(fd.p)
for _, cp := range contained {
mp, ismap := cp.(*mapparm)
if ismap {
b.WriteString(fmt.Sprintf(" %s := mkt.%s\n",
mp.keytmp, mp.keytmp))
b.WriteString(fmt.Sprintf(" _ = %s\n", mp.keytmp))
}
}
}
b.WriteString(fmt.Sprintf(" return %s\n", fd.payload))
b.WriteString("}\n")
}
s.newGenvalFuncs = nil
}
func (s *genstate) emitAddrTakenHelpers(f *funcdef, b *bytes.Buffer, emit bool) {
b.WriteString("// begin addr taken helpers\n")
s.emitDerefFuncs(b, emit)
s.emitAssignFuncs(b, emit)
s.emitNewFuncs(b, emit)
s.emitGlobalVars(b, emit)
s.emitGenValFuncs(f, b, emit)
b.WriteString("// end addr taken helpers\n")
}
func (s *genstate) genGlobVar(p parm) string {
var pp parm
ppp := mkPointerParm(p)
pp = &ppp
b := bytes.NewBuffer(nil)
pp.Declare(b, "gv", "", false)
tag := b.String()
gv, ok := s.globVars[tag]
if ok {
return gv
}
gv = fmt.Sprintf("gvar_%d", len(s.globVars))
s.newGlobVars = append(s.newGlobVars, funcdesc{pp: pp, p: p, name: gv, tag: tag})
s.globVars[tag] = gv
return gv
}
func (s *genstate) genParamDerefFunc(p parm) string {
var pp parm
ppp := mkPointerParm(p)
pp = &ppp
b := bytes.NewBuffer(nil)
pp.Declare(b, "x", "", false)
tag := b.String()
f, ok := s.derefFuncs[tag]
if ok {
return f
}
f = fmt.Sprintf("deref_%d", len(s.derefFuncs))
s.newDerefFuncs = append(s.newDerefFuncs, funcdesc{pp: pp, p: p, name: f, tag: tag})
s.derefFuncs[tag] = f
return f
}
func (s *genstate) genAssignFunc(p parm) string {
var pp parm
ppp := mkPointerParm(p)
pp = &ppp
b := bytes.NewBuffer(nil)
pp.Declare(b, "x", "", false)
tag := b.String()
f, ok := s.assignFuncs[tag]
if ok {
return f
}
f = fmt.Sprintf("retassign_%d", len(s.assignFuncs))
s.newAssignFuncs = append(s.newAssignFuncs, funcdesc{pp: pp, p: p, name: f, tag: tag})
s.assignFuncs[tag] = f
return f
}
func (s *genstate) genAllocFunc(p parm) string {
var pp parm
ppp := mkPointerParm(p)
pp = &ppp
b := bytes.NewBuffer(nil)
pp.Declare(b, "x", "", false)
tag := b.String()
f, ok := s.allocFuncs[tag]
if ok {
return f
}
f = fmt.Sprintf("New_%d", len(s.allocFuncs))
s.newAllocFuncs = append(s.newAllocFuncs, funcdesc{pp: pp, p: p, name: f, tag: tag})
s.allocFuncs[tag] = f
return f
}
func (s *genstate) genParamRef(p parm, idx int) string {
switch p.AddrTaken() {
case notAddrTaken:
return fmt.Sprintf("p%d", idx)
case addrTakenSimple, addrTakenHeap:
return fmt.Sprintf("(*ap%d)", idx)
case addrTakenPassed:
f := s.genParamDerefFunc(p)
return fmt.Sprintf("%s(ap%d)", f, idx)
default:
panic("bad")
}
}
func (s *genstate) genReturnAssign(b *bytes.Buffer, r parm, idx int, val string) {
switch r.AddrTaken() {
case notAddrTaken:
b.WriteString(fmt.Sprintf(" r%d = %s\n", idx, val))
case addrTakenSimple, addrTakenHeap:
b.WriteString(fmt.Sprintf(" (*ar%d) = %v\n", idx, val))
case addrTakenPassed:
f := s.genAssignFunc(r)
b.WriteString(fmt.Sprintf(" %s(ar%d, %v)\n", f, idx, val))
default:
panic("bad")
}
}
func (s *genstate) emitParamElemCheck(f *funcdef, b *bytes.Buffer, p parm, pvar string, cvar string, paramidx int, elemidx int) {
if p.SkipCompare() == SkipAll {
b.WriteString(fmt.Sprintf(" // selective skip of %s\n", pvar))
b.WriteString(fmt.Sprintf(" _ = %s\n", cvar))
return
} else if p.SkipCompare() == SkipPayload {
switch p.(type) {
case *stringparm, *arrayparm:
b.WriteString(fmt.Sprintf(" if len(%s) != len(%s) { // skip payload\n",
pvar, cvar))
default:
panic("should never happen")
}
} else {
basep, star := genDeref(p)
// Handle *p where p is an empty struct.
if basep.NumElements() == 0 {
return
}
if basep.HasPointer() {
efn := s.eqFuncRef(f, basep, false)
b.WriteString(fmt.Sprintf(" if !%s(%s%s, %s%s) {\n",
efn, star, pvar, star, cvar))
} else {
b.WriteString(fmt.Sprintf(" if %s%s != %s%s {\n",
star, pvar, star, cvar))
}
}
cm := f.complexityMeasure()
b.WriteString(fmt.Sprintf(" NoteFailureElem(%d, %d, %d, \"%s\", \"parm\", %d, %d, false, pad[0])\n", cm, s.pkidx, f.idx, s.checkerPkg(s.pkidx), paramidx, elemidx))
b.WriteString(" return\n")
b.WriteString(" }\n")
}
func (s *genstate) emitParamChecks(f *funcdef, b *bytes.Buffer, pidx int, value int) (int, bool) {
var valstr string
haveControl := false
dangling := []int{}
for pi, p := range f.params {
verb(4, "emitting parmcheck p%d numel=%d pt=%s value=%d",
pi, p.NumElements(), p.TypeName(), value)
// To balance code in caller
_ = uint8(s.wr.Intn(100)) < 50
if p.IsControl() {
b.WriteString(fmt.Sprintf(" if %s == 0 {\n",
s.genParamRef(p, pi)))
s.emitReturn(f, b, false)
b.WriteString(" }\n")
haveControl = true
} else if p.IsBlank() {
valstr, value = s.GenValue(f, p, value, false)
if f.recur {
b.WriteString(fmt.Sprintf(" brc%d := %s\n", pi, valstr))
} else {
b.WriteString(fmt.Sprintf(" _ = %s\n", valstr))
}
} else {
numel := p.NumElements()
cel := checkableElements(p)
for i := range numel {
verb(4, "emitting check-code for p%d el %d value=%d", pi, i, value)
elref, elparm := p.GenElemRef(i, s.genParamRef(p, pi))
valstr, value = s.GenValue(f, elparm, value, false)
if elref == "" || elref == "_" || cel == 0 {
b.WriteString(fmt.Sprintf(" // blank skip: %s\n", valstr))
continue
} else {
basep, _ := genDeref(elparm)
// Handle *p where p is an empty struct.
if basep.NumElements() == 0 {
continue
}
cvar := fmt.Sprintf("p%df%dc", pi, i)
b.WriteString(fmt.Sprintf(" %s := %s\n", cvar, valstr))
s.emitParamElemCheck(f, b, elparm, elref, cvar, pi, i)
}
}
if p.AddrTaken() != notAddrTaken {
dangling = append(dangling, pi)
}
}
if value != f.values[pi] {
fmt.Fprintf(os.Stderr, "internal error: checker/caller value mismatch after emitting param %d func Test%d pkg %s: caller %d checker %d\n", pi, f.idx, s.checkerPkg(pidx), f.values[pi], value)
s.errs++
}
}
for _, pi := range dangling {
b.WriteString(fmt.Sprintf(" _ = ap%d // ref\n", pi))
}
// receiver value check
if f.isMethod {
numel := f.receiver.NumElements()
for i := range numel {
verb(4, "emitting check-code for rcvr el %d value=%d", i, value)
elref, elparm := f.receiver.GenElemRef(i, "rcvr")
valstr, value = s.GenValue(f, elparm, value, false)
if elref == "" || strings.HasPrefix(elref, "_") || f.receiver.IsBlank() {
verb(4, "empty skip rcvr el %d", i)
continue
} else {
basep, _ := genDeref(elparm)
// Handle *p where p is an empty struct.
if basep.NumElements() == 0 {
continue
}
cvar := fmt.Sprintf("rcvrf%dc", i)
b.WriteString(fmt.Sprintf(" %s := %s\n", cvar, valstr))
s.emitParamElemCheck(f, b, elparm, elref, cvar, -1, i)
}
}
}
return value, haveControl
}
// emitDeferChecks creates code like
//
// defer func(...args...) {
// check arg
// check param
// }(...)
//
// where we randomly choose to either pass a param through to the
// function literal, or have the param captured by the closure, then
// check its value in the defer.
func (s *genstate) emitDeferChecks(f *funcdef, b *bytes.Buffer, value int) int {
if len(f.params) == 0 {
return value
}
// make a pass through the params and randomly decide which will be passed into the func.
passed := []bool{}
for i := range f.params {
p := f.dodefp[i] < 50
passed = append(passed, p)
}
b.WriteString(" defer func(")
pc := 0
for pi, p := range f.params {
if p.IsControl() || p.IsBlank() {
continue
}
if passed[pi] {
writeCom(b, pc)
n := fmt.Sprintf("p%d", pi)
p.Declare(b, n, "", false)
pc++
}
}
b.WriteString(") {\n")
for pi, p := range f.params {
if p.IsControl() || p.IsBlank() {
continue
}
which := "passed"
if !passed[pi] {
which = "captured"
}
b.WriteString(" // check parm " + which + "\n")
numel := p.NumElements()
cel := checkableElements(p)
for i := range numel {
elref, elparm := p.GenElemRef(i, s.genParamRef(p, pi))
if elref == "" || elref == "_" || cel == 0 {
verb(4, "empty skip p%d el %d", pi, i)
continue
} else {
basep, _ := genDeref(elparm)
// Handle *p where p is an empty struct.
if basep.NumElements() == 0 {
continue
}
cvar := fmt.Sprintf("p%df%dc", pi, i)
s.emitParamElemCheck(f, b, elparm, elref, cvar, pi, i)
}
}
}
b.WriteString(" } (")
pc = 0
for pi, p := range f.params {
if p.IsControl() || p.IsBlank() {
continue
}
if passed[pi] {
writeCom(b, pc)
b.WriteString(fmt.Sprintf("p%d", pi))
pc++
}
}
b.WriteString(")\n\n")
return value
}
func (s *genstate) emitVarAssign(f *funcdef, b *bytes.Buffer, r parm, rname string, value int, caller bool) int {
var valstr string
isassign := uint8(s.wr.Intn(100)) < 50
if rmp, ismap := r.(*mapparm); ismap && isassign {
// emit: var m ... ; m[k] = v
r.Declare(b, " "+rname+" := make(", ")\n", caller)
valstr, value = s.GenValue(f, rmp.valtype, value, caller)
b.WriteString(fmt.Sprintf(" %s[mkt.%s] = %s\n",
rname, rmp.keytmp, valstr))
} else {
// emit r = c
valstr, value = s.GenValue(f, r, value, caller)
b.WriteString(fmt.Sprintf(" %s := %s\n", rname, valstr))
}
return value
}
func (s *genstate) emitChecker(f *funcdef, b *bytes.Buffer, pidx int, emit bool) {
verb(4, "emitting struct and array defs")
s.emitStructAndArrayDefs(f, b)
b.WriteString(fmt.Sprintf("// %d returns %d params\n", len(f.returns), len(f.params)))
if s.Pragma != "" {
b.WriteString("//go:" + s.Pragma + "\n")
}
b.WriteString("//go:noinline\n")
b.WriteString("func")
if f.isMethod {
b.WriteString(" (")
n := "rcvr"
if f.receiver.IsBlank() {
n = "_"
}
f.receiver.Declare(b, n, "", false)
b.WriteString(")")
}
b.WriteString(fmt.Sprintf(" Test%d(", f.idx))
verb(4, "emitting checker p%d/Test%d", pidx, f.idx)
// params
for pi, p := range f.params {
writeCom(b, pi)
n := fmt.Sprintf("p%d", pi)
if p.IsBlank() {
n = "_"
}
p.Declare(b, n, "", false)
}
b.WriteString(") ")
// returns
if len(f.returns) > 0 {
b.WriteString("(")
}
for ri, r := range f.returns {
writeCom(b, ri)
r.Declare(b, fmt.Sprintf("r%d", ri), "", false)
}
if len(f.returns) > 0 {
b.WriteString(")")
}
b.WriteString(" {\n")
// local storage
b.WriteString(" // consume some stack space, so as to trigger morestack\n")
b.WriteString(fmt.Sprintf(" var pad [%d]uint64\n", f.rstack))
b.WriteString(fmt.Sprintf(" pad[FailCount[%d] & 0x1]++\n", pidx))
value := 1
// generate map key tmps
s.wr.Checkpoint("before map key temps")
value = s.emitMapKeyTmps(f, b, pidx, value, false)
// generate return constants
s.wr.Checkpoint("before return constants")
for ri, r := range f.returns {
rc := fmt.Sprintf("rc%d", ri)
value = s.emitVarAssign(f, b, r, rc, value, false)
}
// Prepare to reference params/returns by address.
lists := [][]parm{f.params, f.returns}
names := []string{"p", "r"}
var aCounts [2]int
for i, lst := range lists {
for pi, p := range lst {
if p.AddrTaken() == notAddrTaken {
continue
}
aCounts[i]++
n := names[i]
b.WriteString(fmt.Sprintf(" a%s%d := &%s%d\n", n, pi, n, pi))
if p.AddrTaken() == addrTakenHeap {
gv := s.genGlobVar(p)
b.WriteString(fmt.Sprintf(" %s = a%s%d\n", gv, n, pi))
}
}
}
if s.EmitBad == 2 {
if s.BadPackageIdx == pidx && s.BadFuncIdx == f.idx {
b.WriteString(" // force runtime failure here (debugging)\n")
b.WriteString(fmt.Sprintf(" NoteFailure(%d, %d, %d, \"%s\", \"artificial\", %d, true, uint64(0))\n", f.complexityMeasure(), pidx, f.idx, s.checkerPkg(pidx), 0))
}
}
// parameter checking code
var haveControl bool
s.wr.Checkpoint("before param checks")
value, haveControl = s.emitParamChecks(f, b, pidx, value)
// defer testing
if s.tunables.doDefer && f.dodefc < s.tunables.deferFraction {
s.wr.Checkpoint("before defer checks")
_ = s.emitDeferChecks(f, b, value)
}
// returns
s.emitReturn(f, b, haveControl)
b.WriteString(fmt.Sprintf(" // %d addr-taken params, %d addr-taken returns\n",
aCounts[0], aCounts[1]))
b.WriteString("}\n\n")
// emit any new helper funcs referenced by this test function
s.emitAddrTakenHelpers(f, b, emit)
}
// complexityMeasure returns an integer that estimates how complex a
// given test function is relative to some other function. The more
// parameters + returns and the more complicated the types of the
// params/returns, the higher the number returned here. In theory this
// could be worked into the minimization process (e.g. pick the least
// complex func that reproduces the failure), but for now that isn't
// wired up yet.
func (f *funcdef) complexityMeasure() int {
v := int(0)
if f.isMethod {
v += f.receiver.NumElements()
}
for _, p := range f.params {
v += p.NumElements()
}
for _, r := range f.returns {
v += r.NumElements()
}
return v
}
// emitRecursiveCall generates a recursive call to the test function in question.
func (s *genstate) emitRecursiveCall(f *funcdef) string {
b := bytes.NewBuffer(nil)
rcvr := ""
if f.isMethod {
rcvr = "rcvr."
}
b.WriteString(fmt.Sprintf(" %sTest%d(", rcvr, f.idx))
for pi, p := range f.params {
writeCom(b, pi)
if p.IsControl() {
b.WriteString(fmt.Sprintf(" %s-1", s.genParamRef(p, pi)))
} else {
if !p.IsBlank() {
b.WriteString(fmt.Sprintf(" %s", s.genParamRef(p, pi)))
} else {
b.WriteString(fmt.Sprintf(" brc%d", pi))
}
}
}
b.WriteString(")")
return b.String()
}
// emitReturn generates a return sequence.
func (s *genstate) emitReturn(f *funcdef, b *bytes.Buffer, doRecursiveCall bool) {
// If any of the return values are address-taken, then instead of
//
// return x, y, z
//
// we emit
//
// r1 = ...
// r2 = ...
// ...
// return
//
// Make an initial pass through the returns to see if we need to do this.
// Figure out the final return values in the process.
indirectReturn := false
retvals := []string{}
for ri, r := range f.returns {
if r.AddrTaken() != notAddrTaken {
indirectReturn = true
}
t := ""
if doRecursiveCall {
t = "t"
}
retvals = append(retvals, fmt.Sprintf("rc%s%d", t, ri))
}
// generate the recursive call itself if applicable
if doRecursiveCall {
b.WriteString(" // recursive call\n ")
if s.ForceStackGrowth {
b.WriteString(" hackStack() // force stack growth on next call\n")
}
rcall := s.emitRecursiveCall(f)
if indirectReturn {
for ri := range f.returns {
writeCom(b, ri)
b.WriteString(fmt.Sprintf(" rct%d", ri))
}
b.WriteString(" := ")
b.WriteString(rcall)
b.WriteString("\n")
} else {
if len(f.returns) == 0 {
b.WriteString(fmt.Sprintf("%s\n return\n", rcall))
} else {
b.WriteString(fmt.Sprintf(" return %s\n", rcall))
}
return
}
}
// now the actual return
if indirectReturn {
for ri, r := range f.returns {
s.genReturnAssign(b, r, ri, retvals[ri])
}
b.WriteString(" return\n")
} else {
b.WriteString(" return ")
for ri := range f.returns {
writeCom(b, ri)
b.WriteString(retvals[ri])
}
b.WriteString("\n")
}
}
func (s *genstate) GenPair(calloutfile *os.File, checkoutfile *os.File, fidx int, pidx int, b *bytes.Buffer, seed int64, emit bool) int64 {
verb(1, "gen fidx %d pidx %d", fidx, pidx)
checkTunables(tunables)
s.tunables = tunables
// Generate a function with a random number of params and returns
s.wr = NewWrapRand(seed, s.RandCtl)
s.wr.tag = "genfunc"
fp := s.GenFunc(fidx, pidx)
// Emit caller side
wrcaller := NewWrapRand(seed, s.RandCtl)
s.wr = wrcaller
s.wr.tag = "caller"
s.emitCaller(fp, b, pidx)
if emit {
b.WriteTo(calloutfile)
}
b.Reset()
// Emit checker side
wrchecker := NewWrapRand(seed, s.RandCtl)
s.wr = wrchecker
s.wr.tag = "checker"
s.emitChecker(fp, b, pidx, emit)
if emit {
b.WriteTo(checkoutfile)
}
b.Reset()
wrchecker.Check(wrcaller)
return seed + 1
}
func (s *genstate) openOutputFile(filename string, pk string, imports []string, ipref string) *os.File {
iprefix := func(f string) string {
if ipref == "" {
return f
}
return ipref + "/" + f
}
verb(1, "opening %s", filename)
outf, err := os.OpenFile(filename, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666)
if err != nil {
log.Fatal(err)
}
haveunsafe := false
outf.WriteString(fmt.Sprintf("package %s\n\n", pk))
for _, imp := range imports {
if imp == "reflect" {
outf.WriteString("import \"reflect\"\n")
continue
}
if imp == "unsafe" {
outf.WriteString("import _ \"unsafe\"\n")
haveunsafe = true
continue
}
if imp == s.utilsPkg() {
outf.WriteString(fmt.Sprintf("import . \"%s\"\n", iprefix(imp)))
continue
}
outf.WriteString(fmt.Sprintf("import \"%s\"\n", iprefix(imp)))
}
outf.WriteString("\n")
if s.ForceStackGrowth && haveunsafe {
outf.WriteString("// Hack: reach into runtime to grab this testing hook.\n")
outf.WriteString("//go:linkname hackStack runtime.gcTestMoveStackOnNextCall\n")
outf.WriteString("func hackStack()\n\n")
}
return outf
}
type miscVals struct {
NumTpk int
MaxFail int
NumTests int
}
const utilsTemplate = `
import (
"fmt"
"os"
)
type UtilsType int
var ParamFailCount [{{.NumTpk}}]int
var ReturnFailCount [{{.NumTpk}}]int
var FailCount [{{.NumTpk}}]int
var Mode [{{.NumTpk}}]string
//go:noinline
func NoteFailure(cm int, pidx int, fidx int, pkg string, pref string, parmNo int, isret bool, _ uint64) {
if isret {
if ParamFailCount[pidx] != 0 {
return
}
ReturnFailCount[pidx]++
} else {
ParamFailCount[pidx]++
}
fmt.Fprintf(os.Stderr, "Error: fail %s |%d|%d|%d| =%s.Test%d= %s %d\n", Mode, cm, pidx, fidx, pkg, fidx, pref, parmNo)
if ParamFailCount[pidx]+FailCount[pidx]+ReturnFailCount[pidx] > {{.MaxFail}} {
os.Exit(1)
}
}
//go:noinline
func NoteFailureElem(cm int, pidx int, fidx int, pkg string, pref string, parmNo int, elem int, isret bool, _ uint64) {
if isret {
if ParamFailCount[pidx] != 0 {
return
}
ReturnFailCount[pidx]++
} else {
ParamFailCount[pidx]++
}
fmt.Fprintf(os.Stderr, "Error: fail %s |%d|%d|%d| =%s.Test%d= %s %d elem %d\n", Mode, cm, pidx, fidx, pkg, fidx, pref, parmNo, elem)
if ParamFailCount[pidx]+FailCount[pidx]+ReturnFailCount[pidx] > {{.MaxFail}} {
os.Exit(1)
}
}
func BeginFcn(p int) {
ParamFailCount[p] = 0
ReturnFailCount[p] = 0
}
func EndFcn(p int) {
FailCount[p] += ParamFailCount[p]
FailCount[p] += ReturnFailCount[p]
}
`
func (s *genstate) emitUtils(outf *os.File, maxfail int, numtpk int) {
vals := miscVals{
NumTpk: numtpk,
MaxFail: maxfail,
}
t := template.Must(template.New("utils").Parse(utilsTemplate))
err := t.Execute(outf, vals)
if err != nil {
log.Fatal(err)
}
}
const mainPreamble = `
import (
"fmt"
"os"
)
func main() {
fmt.Fprintf(os.Stderr, "starting main\n")
`
func (s *genstate) emitMain(outf *os.File, numit int, fcnmask map[int]int, pkmask map[int]int) {
fmt.Fprintf(outf, "%s", mainPreamble)
fmt.Fprintf(outf, " pch := make(chan bool, %d)\n", s.NumTestPackages)
for k := 0; k < s.NumTestPackages; k++ {
cp := fmt.Sprintf("%s%s%d", s.Tag, CallerName, k)
fmt.Fprintf(outf, " go func(ch chan bool) {\n")
for i := range numit {
if shouldEmitFP(i, k, fcnmask, pkmask) {
fmt.Fprintf(outf, " %s.%s%d(\"normal\")\n", cp, CallerName, i)
if s.tunables.doReflectCall {
fmt.Fprintf(outf, " %s.%s%d(\"reflect\")\n", cp, CallerName, i)
}
}
}
fmt.Fprintf(outf, " pch <- true\n")
fmt.Fprintf(outf, " }(pch)\n")
}
fmt.Fprintf(outf, " for pidx := 0; pidx < %d; pidx++ {\n", s.NumTestPackages)
fmt.Fprintf(outf, " _ = <- pch\n")
fmt.Fprintf(outf, " }\n")
fmt.Fprintf(outf, " tf := 0\n")
fmt.Fprintf(outf, " for pidx := 0; pidx < %d; pidx++ {\n", s.NumTestPackages)
fmt.Fprintf(outf, " tf += FailCount[pidx]\n")
fmt.Fprintf(outf, " }\n")
fmt.Fprintf(outf, " if tf != 0 {\n")
fmt.Fprintf(outf, " fmt.Fprintf(os.Stderr, \"FAILURES: %%d\\n\", tf)\n")
fmt.Fprintf(outf, " os.Exit(2)\n")
fmt.Fprintf(outf, " }\n")
fmt.Fprintf(outf, " fmt.Fprintf(os.Stderr, \"finished %d tests\\n\")\n", numit*s.NumTestPackages)
fmt.Fprintf(outf, "}\n")
}
func makeDir(d string) {
fi, err := os.Stat(d)
if err == nil && fi.IsDir() {
return
}
verb(1, "creating %s", d)
if err := os.Mkdir(d, 0777); err != nil {
log.Fatal(err)
}
}
func (s *genstate) callerPkg(which int) string {
return s.Tag + CallerName + strconv.Itoa(which)
}
func (s *genstate) callerFile(which int) string {
cp := s.callerPkg(which)
return filepath.Join(s.OutDir, cp, cp+".go")
}
func (s *genstate) checkerPkg(which int) string {
return s.Tag + CheckerName + strconv.Itoa(which)
}
func (s *genstate) checkerFile(which int) string {
cp := s.checkerPkg(which)
return filepath.Join(s.OutDir, cp, cp+".go")
}
func (s *genstate) utilsPkg() string {
return s.Tag + "Utils"
}
func (s *genstate) beginPackage(pkidx int) {
s.pkidx = pkidx
s.derefFuncs = make(map[string]string)
s.assignFuncs = make(map[string]string)
s.allocFuncs = make(map[string]string)
s.globVars = make(map[string]string)
s.genvalFuncs = make(map[string]string)
}
func runImports(files []string) {
verb(1, "... running goimports")
args := make([]string, 0, len(files)+1)
args = append(args, "-w")
args = append(args, files...)
cmd := exec.Command("goimports", args...)
coutput, cerr := cmd.CombinedOutput()
if cerr != nil {
log.Fatalf("goimports command failed: %s", string(coutput))
}
verb(1, "... goimports run complete")
}
// shouldEmitFP returns true if we should actually emit code for the function
// with the specified package + fcn indices. For "regular" runs, fcnmask and pkmask
// will be empty, meaning we want to emit every function in every package. The
// fuzz-runner program also tries to do testcase "minimization", which means that it
// will try to whittle down the set of packages and functions (by running the generator
// using the fcnmask and pkmask options) to emit only specific packages or functions.
func shouldEmitFP(fn int, pk int, fcnmask map[int]int, pkmask map[int]int) bool {
emitpk := true
emitfn := true
if len(pkmask) != 0 {
emitpk = false
if _, ok := pkmask[pk]; ok {
emitpk = true
}
}
if len(fcnmask) != 0 {
emitfn = false
if _, ok := fcnmask[fn]; ok {
emitfn = true
}
}
doemit := emitpk && emitfn
verb(2, "shouldEmitFP(F=%d,P=%d) returns %v", fn, pk, doemit)
return doemit
}
// Generate is the top level code generation hook for this package.
// Emits code according to the schema in config object 'c'.
func Generate(c GenConfig) int {
mainpkg := c.Tag + "Main"
var ipref string
if len(c.PkgPath) > 0 {
ipref = c.PkgPath
}
s := genstate{
GenConfig: c,
ipref: ipref,
}
if s.OutDir != "." {
verb(1, "creating %s", s.OutDir)
makeDir(s.OutDir)
}
mainimports := []string{}
for i := 0; i < s.NumTestPackages; i++ {
if shouldEmitFP(-1, i, nil, s.PkgMask) {
makeDir(s.OutDir + "/" + s.callerPkg(i))
makeDir(s.OutDir + "/" + s.checkerPkg(i))
makeDir(s.OutDir + "/" + s.utilsPkg())
mainimports = append(mainimports, s.callerPkg(i))
}
}
mainimports = append(mainimports, s.utilsPkg())
// Emit utils package.
verb(1, "emit utils")
utilsfile := s.OutDir + "/" + s.utilsPkg() + "/" + s.utilsPkg() + ".go"
utilsoutfile := s.openOutputFile(utilsfile, s.utilsPkg(), []string{}, "")
s.emitUtils(utilsoutfile, s.MaxFail, s.NumTestPackages)
utilsoutfile.Close()
mainfile := s.OutDir + "/" + mainpkg + ".go"
mainoutfile := s.openOutputFile(mainfile, "main", mainimports, ipref)
allfiles := []string{mainfile, utilsfile}
for k := 0; k < s.NumTestPackages; k++ {
callerImports := []string{s.checkerPkg(k), s.utilsPkg()}
checkerImports := []string{s.utilsPkg()}
if tunables.doReflectCall {
callerImports = append(callerImports, "reflect")
}
if s.ForceStackGrowth {
callerImports = append(callerImports, "unsafe")
checkerImports = append(checkerImports, "unsafe")
}
var calleroutfile, checkeroutfile *os.File
if shouldEmitFP(-1, k, nil, s.PkgMask) {
calleroutfile = s.openOutputFile(s.callerFile(k), s.callerPkg(k),
callerImports, ipref)
checkeroutfile = s.openOutputFile(s.checkerFile(k), s.checkerPkg(k),
checkerImports, ipref)
allfiles = append(allfiles, s.callerFile(k), s.checkerFile(k))
}
s.beginPackage(k)
var b bytes.Buffer
for i := 0; i < s.NumTestFunctions; i++ {
doemit := shouldEmitFP(i, k, s.FcnMask, s.PkgMask)
s.Seed = s.GenPair(calleroutfile, checkeroutfile, i, k,
&b, s.Seed, doemit)
}
// When minimization is in effect, we sometimes wind
// up eliminating all refs to the utils package. Add a
// dummy to help with this.
fmt.Fprintf(calleroutfile, "\n// dummy\nvar Dummy UtilsType\n")
fmt.Fprintf(checkeroutfile, "\n// dummy\nvar Dummy UtilsType\n")
calleroutfile.Close()
checkeroutfile.Close()
}
s.emitMain(mainoutfile, s.NumTestFunctions, s.FcnMask, s.PkgMask)
// emit go.mod
verb(1, "opening go.mod")
fn := s.OutDir + "/go.mod"
outf, err := os.OpenFile(fn, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666)
if err != nil {
log.Fatal(err)
}
outf.WriteString(fmt.Sprintf("module %s\n\ngo 1.17\n", s.PkgPath))
outf.Close()
verb(1, "closing files")
mainoutfile.Close()
if s.errs == 0 && s.RunGoImports {
runImports(allfiles)
}
return s.errs
}
================================================
FILE: cmd/signature-fuzzer/internal/fuzz-generator/mapparm.go
================================================
// Copyright 2021 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package generator
import (
"bytes"
"fmt"
)
// mapparm describes a parameter of map type; it implements the
// "parm" interface.
type mapparm struct {
aname string
qname string
keytype parm
valtype parm
keytmp string
isBlank
addrTakenHow
isGenValFunc
skipCompare
}
func (p mapparm) IsControl() bool {
return false
}
func (p mapparm) TypeName() string {
return p.aname
}
func (p mapparm) QualName() string {
return p.qname
}
func (p mapparm) Declare(b *bytes.Buffer, prefix string, suffix string, caller bool) {
n := p.aname
if caller {
n = p.qname
}
b.WriteString(fmt.Sprintf("%s %s%s", prefix, n, suffix))
}
func (p mapparm) String() string {
return fmt.Sprintf("%s map[%s]%s", p.aname,
p.keytype.String(), p.valtype.String())
}
func (p mapparm) GenValue(s *genstate, f *funcdef, value int, caller bool) (string, int) {
var buf bytes.Buffer
verb(5, "mapparm.GenValue(%d)", value)
n := p.aname
if caller {
n = p.qname
}
buf.WriteString(fmt.Sprintf("%s{", n))
buf.WriteString(p.keytmp + ": ")
var valstr string
valstr, value = s.GenValue(f, p.valtype, value, caller)
buf.WriteString(valstr + "}")
return buf.String(), value
}
func (p mapparm) GenElemRef(elidx int, path string) (string, parm) {
vne := p.valtype.NumElements()
verb(4, "begin GenElemRef(%d,%s) on %s %d", elidx, path, p.String(), vne)
ppath := fmt.Sprintf("%s[mkt.%s]", path, p.keytmp)
// otherwise dig into the value
verb(4, "recur GenElemRef(%d,...)", elidx)
// Otherwise our victim is somewhere inside the value
if p.IsBlank() {
ppath = "_"
}
return p.valtype.GenElemRef(elidx, ppath)
}
func (p mapparm) NumElements() int {
return p.valtype.NumElements()
}
func (p mapparm) HasPointer() bool {
return true
}
================================================
FILE: cmd/signature-fuzzer/internal/fuzz-generator/numparm.go
================================================
// Copyright 2021 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package generator
import (
"bytes"
"fmt"
"math"
)
// numparm describes a numeric parameter type; it implements the
// "parm" interface.
type numparm struct {
tag string
widthInBits uint32
ctl bool
isBlank
addrTakenHow
isGenValFunc
skipCompare
}
var f32parm *numparm = &numparm{
tag: "float",
widthInBits: uint32(32),
ctl: false,
}
var f64parm *numparm = &numparm{
tag: "float",
widthInBits: uint32(64),
ctl: false,
}
func (p numparm) TypeName() string {
if p.tag == "byte" {
return "byte"
}
return fmt.Sprintf("%s%d", p.tag, p.widthInBits)
}
func (p numparm) QualName() string {
return p.TypeName()
}
func (p numparm) String() string {
if p.tag == "byte" {
return "byte"
}
ctl := ""
if p.ctl {
ctl = " [ctl=yes]"
}
return fmt.Sprintf("%s%s", p.TypeName(), ctl)
}
func (p numparm) NumElements() int {
return 1
}
func (p numparm) IsControl() bool {
return p.ctl
}
func (p numparm) GenElemRef(elidx int, path string) (string, parm) {
return path, &p
}
func (p numparm) Declare(b *bytes.Buffer, prefix string, suffix string, caller bool) {
t := fmt.Sprintf("%s%d%s", p.tag, p.widthInBits, suffix)
if p.tag == "byte" {
t = fmt.Sprintf("%s%s", p.tag, suffix)
}
b.WriteString(prefix + " " + t)
}
func (p numparm) genRandNum(s *genstate, value int) (string, int) {
which := uint8(s.wr.Intn(int64(100)))
if p.tag == "int" {
var v int64
if which < 3 {
// max
v = (1 << (p.widthInBits - 1)) - 1
} else if which < 5 {
// min
v = (-1 << (p.widthInBits - 1))
} else {
nrange := int64(1 << (p.widthInBits - 2))
v = s.wr.Intn(nrange)
if value%2 != 0 {
v = -v
}
}
return fmt.Sprintf("%s%d(%d)", p.tag, p.widthInBits, v), value + 1
}
if p.tag == "uint" || p.tag == "byte" {
nrange := int64(1 << (p.widthInBits - 2))
v := s.wr.Intn(nrange)
if p.tag == "byte" {
return fmt.Sprintf("%s(%d)", p.tag, v), value + 1
}
return fmt.Sprintf("%s%d(0x%x)", p.tag, p.widthInBits, v), value + 1
}
if p.tag == "float" {
if p.widthInBits == 32 {
rf := s.wr.Float32() * (math.MaxFloat32 / 4)
if value%2 != 0 {
rf = -rf
}
return fmt.Sprintf("%s%d(%v)", p.tag, p.widthInBits, rf), value + 1
}
if p.widthInBits == 64 {
return fmt.Sprintf("%s%d(%v)", p.tag, p.widthInBits,
s.wr.NormFloat64()), value + 1
}
panic("unknown float type")
}
if p.tag == "complex" {
if p.widthInBits == 64 {
f1, v2 := f32parm.genRandNum(s, value)
f2, v3 := f32parm.genRandNum(s, v2)
return fmt.Sprintf("complex(%s,%s)", f1, f2), v3
}
if p.widthInBits == 128 {
f1, v2 := f64parm.genRandNum(s, value)
f2, v3 := f64parm.genRandNum(s, v2)
return fmt.Sprintf("complex(%v,%v)", f1, f2), v3
}
panic("unknown complex type")
}
panic("unknown numeric type")
}
func (p numparm) GenValue(s *genstate, f *funcdef, value int, caller bool) (string, int) {
r, nv := p.genRandNum(s, value)
verb(5, "numparm.GenValue(%d) = %s", value, r)
return r, nv
}
func (p numparm) HasPointer() bool {
return false
}
================================================
FILE: cmd/signature-fuzzer/internal/fuzz-generator/parm.go
================================================
// Copyright 2021 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package generator
import (
"bytes"
"fmt"
"os"
"sort"
)
// parm is an interface describing an abstract parameter var or return
// var; there will be concrete types of various sorts that implement
// this interface.
type parm interface {
// Declare emits text containing a declaration of this param
// or return var into the specified buffer. Prefix is a tag to
// prepend before the declaration (for example a variable
// name) followed by a space; suffix is an arbitrary string to
// tack onto the end of the param's type text. Here 'caller'
// is set to true if we're emitting the caller part of a test
// pair as opposed to the checker.
Declare(b *bytes.Buffer, prefix string, suffix string, caller bool)
// GenElemRef returns a pair [X,Y] corresponding to a
// component piece of some composite parm, where X is a string
// forming the reference (ex: ".field" if we're picking out a
// struct field) and Y is a parm object corresponding to the
// type of the element.
GenElemRef(elidx int, path string) (string, parm)
// GenValue constructs a new concrete random value appropriate
// for the type in question and returns it, along with a
// sequence number indicating how many random decisions we had
// to make. Here "s" is the current generator state, "f" is
// the current function we're emitting, value is a sequence
// number indicating how many random decisions have been made
// up until this point, and 'caller' is set to true if we're
// emitting the caller part of a test pair as opposed to the
// checker. Return value is a pair [V,I] where V is the text
// if the value, and I is a new sequence number reflecting any
// additional random choices we had to make. For example, if
// the parm is something like "type Foo struct { f1 int32; f2
// float64 }" then we might expect GenValue to emit something
// like "Foo{int32(-9), float64(123.123)}".
GenValue(s *genstate, f *funcdef, value int, caller bool) (string, int)
// IsControl returns true if this specific param has been marked
// as the single param that controls recursion for a recursive
// checker function. The test code doesn't check this param for a specific
// value, but instead returns early if it has value 0 or decrements it
// on a recursive call.
IsControl() bool
// NumElements returns the total number of discrete elements contained
// in this parm. For non-composite types, this will always be 1.
NumElements() int
// String returns a descriptive string for this parm.
String() string
// TypeName returns the non-qualified type name for this parm.
TypeName() string
// QualName returns a package-qualified type name for this parm.
QualName() string
// HasPointer returns true if this parm is of pointer type, or
// if it is a composite that has a pointer element somewhere inside.
// Strings and slices return true for this hook.
HasPointer() bool
// IsBlank() returns true if the name of this parm is "_" (that is,
// if we randomly chose to make it a blank). SetBlank() is used
// to set the 'blank' property for this parm.
IsBlank() bool
SetBlank(v bool)
// AddrTaken() return a token indicating whether this parm should
// be address taken or not, the nature of the address-taken-ness (see
// below at the def of addrTakenHow). SetAddrTaken is used to set
// the address taken property of the parm.
AddrTaken() addrTakenHow
SetAddrTaken(val addrTakenHow)
// IsGenVal() returns true if the values of this type should
// be obtained by calling a helper func, as opposed to
// emitting code inline (as one would for things like numeric
// types). SetIsGenVal is used to set the gen-val property of
// the parm.
IsGenVal() bool
SetIsGenVal(val bool)
// SkipCompare() returns true if we've randomly decided that
// we don't want to compare the value for this param or
// return. SetSkipCompare is used to set the skip-compare
// property of the parm.
SkipCompare() skipCompare
SetSkipCompare(val skipCompare)
}
type addrTakenHow uint8
const (
// Param not address taken.
notAddrTaken addrTakenHow = 0
// Param address is taken and used for simple reads/writes.
addrTakenSimple addrTakenHow = 1
// Param address is taken and passed to a well-behaved function.
addrTakenPassed addrTakenHow = 2
// Param address is taken and stored to a global var.
addrTakenHeap addrTakenHow = 3
)
func (a *addrTakenHow) AddrTaken() addrTakenHow {
return *a
}
func (a *addrTakenHow) SetAddrTaken(val addrTakenHow) {
*a = val
}
type isBlank bool
func (b *isBlank) IsBlank() bool {
return bool(*b)
}
func (b *isBlank) SetBlank(val bool) {
*b = isBlank(val)
}
type isGenValFunc bool
func (g *isGenValFunc) IsGenVal() bool {
return bool(*g)
}
func (g *isGenValFunc) SetIsGenVal(val bool) {
*g = isGenValFunc(val)
}
type skipCompare int
const (
// Param not address taken.
SkipAll = -1
SkipNone = 0
SkipPayload = 1
)
func (s *skipCompare) SkipCompare() skipCompare {
return skipCompare(*s)
}
func (s *skipCompare) SetSkipCompare(val skipCompare) {
*s = skipCompare(val)
}
// containedParms takes an arbitrary param 'p' and returns a slice
// with 'p' itself plus any component parms contained within 'p'.
func containedParms(p parm) []parm {
visited := make(map[string]parm)
worklist := []parm{p}
addToWork := func(p parm) {
if p == nil {
panic("not expected")
}
if _, ok := visited[p.TypeName()]; !ok {
worklist = append(worklist, p)
}
}
for len(worklist) != 0 {
cp := worklist[0]
worklist = worklist[1:]
if _, ok := visited[cp.TypeName()]; ok {
continue
}
visited[cp.TypeName()] = cp
switch x := cp.(type) {
case *mapparm:
addToWork(x.keytype)
addToWork(x.valtype)
case *structparm:
for _, fld := range x.fields {
addToWork(fld)
}
case *arrayparm:
addToWork(x.eltype)
case *pointerparm:
addToWork(x.totype)
case *typedefparm:
addToWork(x.target)
}
}
rv := []parm{}
for _, v := range visited {
rv = append(rv, v)
}
sort.Slice(rv, func(i, j int) bool {
if rv[i].TypeName() == rv[j].TypeName() {
fmt.Fprintf(os.Stderr, "%d %d %+v %+v %s %s\n", i, j, rv[i], rv[i].String(), rv[j], rv[j].String())
panic("unexpected")
}
return rv[i].TypeName() < rv[j].TypeName()
})
return rv
}
================================================
FILE: cmd/signature-fuzzer/internal/fuzz-generator/pointerparm.go
================================================
// Copyright 2021 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package generator
import (
"bytes"
"fmt"
)
// pointerparm describes a parameter of pointer type; it implements the
// "parm" interface.
type pointerparm struct {
tag string
totype parm
isBlank
addrTakenHow
isGenValFunc
skipCompare
}
func (p pointerparm) Declare(b *bytes.Buffer, prefix string, suffix string, caller bool) {
n := p.totype.TypeName()
if caller {
n = p.totype.QualName()
}
b.WriteString(fmt.Sprintf("%s *%s%s", prefix, n, suffix))
}
func (p pointerparm) GenElemRef(elidx int, path string) (string, parm) {
return path, &p
}
func (p pointerparm) GenValue(s *genstate, f *funcdef, value int, caller bool) (string, int) {
pref := ""
if caller {
pref = s.checkerPkg(s.pkidx) + "."
}
var valstr string
valstr, value = s.GenValue(f, p.totype, value, caller)
fname := s.genAllocFunc(p.totype)
return fmt.Sprintf("%s%s(%s)", pref, fname, valstr), value
}
func (p pointerparm) IsControl() bool {
return false
}
func (p pointerparm) NumElements() int {
return 1
}
func (p pointerparm) String() string {
return fmt.Sprintf("*%s", p.totype)
}
func (p pointerparm) TypeName() string {
return fmt.Sprintf("*%s", p.totype.TypeName())
}
func (p pointerparm) QualName() string {
return fmt.Sprintf("*%s", p.totype.QualName())
}
func mkPointerParm(to parm) pointerparm {
var pp pointerparm
pp.tag = "pointer"
pp.totype = to
return pp
}
func (p pointerparm) HasPointer() bool {
return true
}
================================================
FILE: cmd/signature-fuzzer/internal/fuzz-generator/stringparm.go
================================================
// Copyright 2021 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package generator
import (
"bytes"
)
// stringparm describes a parameter of string type; it implements the
// "parm" interface
type stringparm struct {
tag string
isBlank
addrTakenHow
isGenValFunc
skipCompare
}
func (p stringparm) Declare(b *bytes.Buffer, prefix string, suffix string, caller bool) {
b.WriteString(prefix + " string" + suffix)
}
func (p stringparm) GenElemRef(elidx int, path string) (string, parm) {
return path, &p
}
var letters = []rune("�꿦3f6ꂅ8ˋ<(z̽|ϣᇊq筁{ЂƜĽ")
func (p stringparm) GenValue(s *genstate, f *funcdef, value int, caller bool) (string, int) {
ns := len(letters) - 9
nel := int(s.wr.Intn(8))
st := int(s.wr.Intn(int64(ns)))
en := min(st+nel, ns)
return "\"" + string(letters[st:en]) + "\"", value + 1
}
func (p stringparm) IsControl() bool {
return false
}
func (p stringparm) NumElements() int {
return 1
}
func (p stringparm) String() string {
return "string"
}
func (p stringparm) TypeName() string {
return "string"
}
func (p stringparm) QualName() string {
return "string"
}
func (p stringparm) HasPointer() bool {
return false
}
================================================
FILE: cmd/signature-fuzzer/internal/fuzz-generator/structparm.go
================================================
// Copyright 2021 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package generator
import (
"bytes"
"fmt"
"strings"
)
// structparm describes a parameter of struct type; it implements the
// "parm" interface.
type structparm struct {
sname string
qname string
fields []parm
isBlank
addrTakenHow
isGenValFunc
skipCompare
}
func (p structparm) TypeName() string {
return p.sname
}
func (p structparm) QualName() string {
return p.qname
}
func (p structparm) Declare(b *bytes.Buffer, prefix string, suffix string, caller bool) {
n := p.sname
if caller {
n = p.qname
}
b.WriteString(fmt.Sprintf("%s %s%s", prefix, n, suffix))
}
func (p structparm) FieldName(i int) string {
if p.fields[i].IsBlank() {
return "_"
}
return fmt.Sprintf("F%d", i)
}
func (p structparm) String() string {
var buf bytes.Buffer
buf.WriteString(fmt.Sprintf("struct %s {\n", p.sname))
for fi, f := range p.fields {
buf.WriteString(fmt.Sprintf("%s %s\n", p.FieldName(fi), f.String()))
}
buf.WriteString("}")
return buf.String()
}
func (p structparm) GenValue(s *genstate, f *funcdef, value int, caller bool) (string, int) {
var buf bytes.Buffer
verb(5, "structparm.GenValue(%d)", value)
n := p.sname
if caller {
n = p.qname
}
buf.WriteString(fmt.Sprintf("%s{", n))
nbfi := 0
for fi, fld := range p.fields {
var valstr string
valstr, value = s.GenValue(f, fld, value, caller)
if p.fields[fi].IsBlank() {
buf.WriteString("/* ")
valstr = strings.ReplaceAll(valstr, "/*", "[[")
valstr = strings.ReplaceAll(valstr, "*/", "]]")
} else {
writeCom(&buf, nbfi)
}
buf.WriteString(p.FieldName(fi) + ": ")
buf.WriteString(valstr)
if p.fields[fi].IsBlank() {
buf.WriteString(" */")
} else {
nbfi++
}
}
buf.WriteString("}")
return buf.String(), value
}
func (p structparm) IsControl() bool {
return false
}
func (p structparm) NumElements() int {
ne := 0
for _, f := range p.fields {
ne += f.NumElements()
}
return ne
}
func (p structparm) GenElemRef(elidx int, path string) (string, parm) {
ct := 0
verb(4, "begin GenElemRef(%d,%s) on %s", elidx, path, p.String())
for fi, f := range p.fields {
fne := f.NumElements()
//verb(4, "+ examining field %d fne %d ct %d", fi, fne, ct)
// Empty field. Continue on.
if elidx == ct && fne == 0 {
continue
}
// Is this field the element we're interested in?
if fne == 1 && elidx == ct {
// The field in question may be a composite that has only
// multiple elements but a single non-zero-sized element.
// If this is the case, keep going.
if sp, ok := f.(*structparm); ok {
if len(sp.fields) > 1 {
ppath := fmt.Sprintf("%s.F%d", path, fi)
if p.fields[fi].IsBlank() || path == "_" {
ppath = "_"
}
return f.GenElemRef(elidx-ct, ppath)
}
}
verb(4, "found field %d type %s in GenElemRef(%d,%s)", fi, f.TypeName(), elidx, path)
ppath := fmt.Sprintf("%s.F%d", path, fi)
if p.fields[fi].IsBlank() || path == "_" {
ppath = "_"
}
return ppath, f
}
// Is the element we want somewhere inside this field?
if fne > 1 && elidx >= ct && elidx < ct+fne {
ppath := fmt.Sprintf("%s.F%d", path, fi)
if p.fields[fi].IsBlank() || path == "_" {
ppath = "_"
}
return f.GenElemRef(elidx-ct, ppath)
}
ct += fne
}
panic(fmt.Sprintf("GenElemRef failed for struct %s elidx %d", p.TypeName(), elidx))
}
func (p structparm) HasPointer() bool {
for _, f := range p.fields {
if f.HasPointer() {
return true
}
}
return false
}
================================================
FILE: cmd/signature-fuzzer/internal/fuzz-generator/typedefparm.go
================================================
// Copyright 2021 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package generator
import (
"bytes"
"fmt"
)
// typedefparm describes a parameter that is a typedef of some other
// type; it implements the "parm" interface
type typedefparm struct {
aname string
qname string
target parm
isBlank
addrTakenHow
isGenValFunc
skipCompare
}
func (p typedefparm) Declare(b *bytes.Buffer, prefix string, suffix string, caller bool) {
n := p.aname
if caller {
n = p.qname
}
b.WriteString(fmt.Sprintf("%s %s%s", prefix, n, suffix))
}
func (p typedefparm) GenElemRef(elidx int, path string) (string, parm) {
_, isarr := p.target.(*arrayparm)
_, isstruct := p.target.(*structparm)
_, ismap := p.target.(*mapparm)
rv, rp := p.target.GenElemRef(elidx, path)
// this is hacky, but I don't see a nicer way to do this
if isarr || isstruct || ismap {
return rv, rp
}
rp = &p
return rv, rp
}
func (p typedefparm) GenValue(s *genstate, f *funcdef, value int, caller bool) (string, int) {
n := p.aname
if caller {
n = p.qname
}
rv, v := s.GenValue(f, p.target, value, caller)
rv = n + "(" + rv + ")"
return rv, v
}
func (p typedefparm) IsControl() bool {
return false
}
func (p typedefparm) NumElements() int {
return p.target.NumElements()
}
func (p typedefparm) String() string {
return fmt.Sprintf("%s typedef of %s", p.aname, p.target.String())
}
func (p typedefparm) TypeName() string {
return p.aname
}
func (p typedefparm) QualName() string {
return p.qname
}
func (p typedefparm) HasPointer() bool {
return p.target.HasPointer()
}
func (s *genstate) makeTypedefParm(f *funcdef, target parm, pidx int) parm {
var tdp typedefparm
ns := len(f.typedefs)
tdp.aname = fmt.Sprintf("MyTypeF%dS%d", f.idx, ns)
tdp.qname = fmt.Sprintf("%s.MyTypeF%dS%d", s.checkerPkg(pidx), f.idx, ns)
tdp.target = target
tdp.SetBlank(uint8(s.wr.Intn(100)) < tunables.blankPerc)
f.typedefs = append(f.typedefs, tdp)
return &tdp
}
================================================
FILE: cmd/signature-fuzzer/internal/fuzz-generator/wraprand.go
================================================
// Copyright 2021 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package generator
import (
"fmt"
"math/rand/v2"
"os"
"runtime"
"strings"
)
const (
RandCtlNochecks = 0
RandCtlChecks = 1 << iota
RandCtlCapture
RandCtlPanic
)
func NewWrapRand(seed int64, ctl int) *wraprand {
return &wraprand{seed: seed, ctl: ctl, rand: rand.New(rand.NewPCG(0, uint64(seed)))}
}
type wraprand struct {
f32calls int
f64calls int
intncalls int
seed int64
tag string
calls []string
ctl int
rand *rand.Rand
}
func (w *wraprand) captureCall(tag string, val string) {
var call strings.Builder
fmt.Fprintf(&call, "%s: %s\n", tag, val)
pc := make([]uintptr, 10)
n := runtime.Callers(1, pc)
if n == 0 {
panic("why?")
}
pc = pc[:n] // pass only valid pcs to runtime.CallersFrames
frames := runtime.CallersFrames(pc)
for {
frame, more := frames.Next()
if strings.Contains(frame.File, "testing.") {
break
}
fmt.Fprintf(&call, "%s %s:%d\n", frame.Function, frame.File, frame.Line)
if !more {
break
}
}
w.calls = append(w.calls, call.String())
}
func (w *wraprand) Intn(n int64) int64 {
w.intncalls++
rv := w.rand.Int64N(n)
if w.ctl&RandCtlCapture != 0 {
w.captureCall("Intn", fmt.Sprintf("%d", rv))
}
return rv
}
func (w *wraprand) Float32() float32 {
w.f32calls++
rv := w.rand.Float32()
if w.ctl&RandCtlCapture != 0 {
w.captureCall("Float32", fmt.Sprintf("%f", rv))
}
return rv
}
func (w *wraprand) NormFloat64() float64 {
w.f64calls++
rv := w.rand.NormFloat64()
if w.ctl&RandCtlCapture != 0 {
w.captureCall("NormFloat64", fmt.Sprintf("%f", rv))
}
return rv
}
func (w *wraprand) emitCalls(fn string) {
outf, err := os.OpenFile(fn, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0o666)
if err != nil {
panic(err)
}
for _, c := range w.calls {
fmt.Fprint(outf, c)
}
outf.Close()
}
func (w *wraprand) Equal(w2 *wraprand) bool {
return w.f32calls == w2.f32calls &&
w.f64calls == w2.f64calls &&
w.intncalls == w2.intncalls
}
func (w *wraprand) Check(w2 *wraprand) {
if w.ctl != 0 && !w.Equal(w2) {
fmt.Fprintf(os.Stderr, "wraprand consistency check failed:\n")
t := "w"
if w.tag != "" {
t = w.tag
}
t2 := "w2"
if w2.tag != "" {
t2 = w2.tag
}
fmt.Fprintf(os.Stderr, " %s: {f32:%d f64:%d i:%d}\n", t,
w.f32calls, w.f64calls, w.intncalls)
fmt.Fprintf(os.Stderr, " %s: {f32:%d f64:%d i:%d}\n", t2,
w2.f32calls, w2.f64calls, w2.intncalls)
if w.ctl&RandCtlCapture != 0 {
f := fmt.Sprintf("/tmp/%s.txt", t)
f2 := fmt.Sprintf("/tmp/%s.txt", t2)
w.emitCalls(f)
w2.emitCalls(f2)
fmt.Fprintf(os.Stderr, "=-= emitted calls to %s, %s\n", f, f2)
}
if w.ctl&RandCtlPanic != 0 {
panic("bad")
}
}
}
func (w *wraprand) Checkpoint(tag string) {
if w.ctl&RandCtlCapture != 0 {
w.calls = append(w.calls, "=-=\n"+tag+"\n=-=\n")
}
}
================================================
FILE: cmd/splitdwarf/internal/macho/fat.go
================================================
// Copyright 2014 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package macho
import (
"encoding/binary"
"io"
"os"
)
// A FatFile is a Mach-O universal binary that contains at least one architecture.
type FatFile struct {
Magic uint32
Arches []FatArch
closer io.Closer
}
// A FatArchHeader represents a fat header for a specific image architecture.
type FatArchHeader struct {
Cpu Cpu
SubCpu uint32
Offset uint32
Size uint32
Align uint32
}
const fatArchHeaderSize = 5 * 4
// A FatArch is a Mach-O File inside a FatFile.
type FatArch struct {
FatArchHeader
*File
}
// NewFatFile creates a new FatFile for accessing all the Mach-O images in a
// universal binary. The Mach-O binary is expected to start at position 0 in
// the ReaderAt.
func NewFatFile(r io.ReaderAt) (*FatFile, error) {
var ff FatFile
sr := io.NewSectionReader(r, 0, 1<<63-1)
// Read the fat_header struct, which is always in big endian.
// Start with the magic number.
err := binary.Read(sr, binary.BigEndian, &ff.Magic)
if err != nil {
return nil, formatError(0, "error reading magic number, %v", err)
} else if ff.Magic != MagicFat {
// See if this is a Mach-O file via its magic number. The magic
// must be converted to little endian first though.
var buf [4]byte
binary.BigEndian.PutUint32(buf[:], ff.Magic)
leMagic := binary.LittleEndian.Uint32(buf[:])
if leMagic == Magic32 || leMagic == Magic64 {
return nil, formatError(0, "not a fat Mach-O file, leMagic=0x%x", leMagic)
} else {
return nil, formatError(0, "invalid magic number, leMagic=0x%x", leMagic)
}
}
offset := int64(4)
// Read the number of FatArchHeaders that come after the fat_header.
var narch uint32
err = binary.Read(sr, binary.BigEndian, &narch)
if err != nil {
return nil, formatError(offset, "invalid fat_header %v", err)
}
offset += 4
if narch < 1 {
return nil, formatError(offset, "file contains no images, narch=%d", narch)
}
// Combine the Cpu and SubCpu (both uint32) into a uint64 to make sure
// there are not duplicate architectures.
seenArches := make(map[uint64]bool, narch)
// Make sure that all images are for the same MH_ type.
var machoType HdrType
// Following the fat_header comes narch fat_arch structs that index
// Mach-O images further in the file.
ff.Arches = make([]FatArch, narch)
for i := uint32(0); i < narch; i++ {
fa := &ff.Arches[i]
err = binary.Read(sr, binary.BigEndian, &fa.FatArchHeader)
if err != nil {
return nil, formatError(offset, "invalid fat_arch header, %v", err)
}
offset += fatArchHeaderSize
fr := io.NewSectionReader(r, int64(fa.Offset), int64(fa.Size))
fa.File, err = NewFile(fr)
if err != nil {
return nil, err
}
// Make sure the architecture for this image is not duplicate.
seenArch := (uint64(fa.Cpu) << 32) | uint64(fa.SubCpu)
if o, k := seenArches[seenArch]; o || k {
return nil, formatError(offset, "duplicate architecture cpu=%v, subcpu=%#x", fa.Cpu, fa.SubCpu)
}
seenArches[seenArch] = true
// Make sure the Mach-O type matches that of the first image.
if i == 0 {
machoType = HdrType(fa.Type)
} else {
if HdrType(fa.Type) != machoType {
return nil, formatError(offset, "Mach-O type for architecture #%d (type=%#x) does not match first (type=%#x)", i, fa.Type, machoType)
}
}
}
return &ff, nil
}
// OpenFat opens the named file using os.Open and prepares it for use as a Mach-O
// universal binary.
func OpenFat(name string) (*FatFile, error) {
f, err := os.Open(name)
if err != nil {
return nil, err
}
ff, err := NewFatFile(f)
if err != nil {
f.Close()
return nil, err
}
ff.closer = f
return ff, nil
}
func (ff *FatFile) Close() error {
var err error
if ff.closer != nil {
err = ff.closer.Close()
ff.closer = nil
}
return err
}
================================================
FILE: cmd/splitdwarf/internal/macho/file.go
================================================
// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package macho implements access to Mach-O object files.
package macho
// High level access to low level data structures.
import (
"bytes"
"compress/zlib"
"debug/dwarf"
"encoding/binary"
"fmt"
"io"
"os"
"slices"
"strings"
"unsafe"
)
// A File represents an open Mach-O file.
type File struct {
FileTOC
Symtab *Symtab
Dysymtab *Dysymtab
closer io.Closer
}
type FileTOC struct {
FileHeader
ByteOrder binary.ByteOrder
Loads []Load
Sections []*Section
}
func (t *FileTOC) AddLoad(l Load) {
t.Loads = append(t.Loads, l)
t.NCommands++
t.SizeCommands += l.LoadSize(t)
}
// AddSegment adds segment s to the file table of contents,
// and also zeroes out the segment information with the expectation
// that this will be added next.
func (t *FileTOC) AddSegment(s *Segment) {
t.AddLoad(s)
s.Nsect = 0
s.Firstsect = 0
}
// Adds section to the most recently added Segment
func (t *FileTOC) AddSection(s *Section) {
g := t.Loads[len(t.Loads)-1].(*Segment)
if g.Nsect == 0 {
g.Firstsect = uint32(len(t.Sections))
}
g.Nsect++
t.Sections = append(t.Sections, s)
sectionsize := uint32(unsafe.Sizeof(Section32{}))
if g.Command() == LcSegment64 {
sectionsize = uint32(unsafe.Sizeof(Section64{}))
}
t.SizeCommands += sectionsize
g.Len += sectionsize
}
// A Load represents any Mach-O load command.
type Load interface {
String() string
Command() LoadCmd
LoadSize(*FileTOC) uint32 // Need the TOC for alignment, sigh.
Put([]byte, binary.ByteOrder) int
// command LC_DYLD_INFO_ONLY contains offsets into __LINKEDIT
// e.g., from "otool -l a.out"
//
// Load command 3
// cmd LC_SEGMENT_64
// cmdsize 72
// segname __LINKEDIT
// vmaddr 0x0000000100002000
// vmsize 0x0000000000001000
// fileoff 8192
// filesize 520
// maxprot 0x00000007
// initprot 0x00000001
// nsects 0
// flags 0x0
// Load command 4
// cmd LC_DYLD_INFO_ONLY
// cmdsize 48
// rebase_off 8192
// rebase_size 8
// bind_off 8200
// bind_size 24
// weak_bind_off 0
// weak_bind_size 0
// lazy_bind_off 8224
// lazy_bind_size 16
// export_off 8240
// export_size 48
}
// LoadBytes is the uninterpreted bytes of a Mach-O load command.
type LoadBytes []byte
// A SegmentHeader is the header for a Mach-O 32-bit or 64-bit load segment command.
type SegmentHeader struct {
LoadCmd
Len uint32
Name string // 16 characters or fewer
Addr uint64 // memory address
Memsz uint64 // memory size
Offset uint64 // file offset
Filesz uint64 // number of bytes starting at that file offset
Maxprot uint32
Prot uint32
Nsect uint32
Flag SegFlags
Firstsect uint32
}
// A Segment represents a Mach-O 32-bit or 64-bit load segment command.
type Segment struct {
SegmentHeader
// Embed ReaderAt for ReadAt method.
// Do not embed SectionReader directly
// to avoid having Read and Seek.
// If a client wants Read and Seek it must use
// Open() to avoid fighting over the seek offset
// with other clients.
io.ReaderAt
sr *io.SectionReader
}
func (s *Segment) Put32(b []byte, o binary.ByteOrder) int {
o.PutUint32(b[0*4:], uint32(s.LoadCmd))
o.PutUint32(b[1*4:], s.Len)
putAtMost16Bytes(b[2*4:], s.Name)
o.PutUint32(b[6*4:], uint32(s.Addr))
o.PutUint32(b[7*4:], uint32(s.Memsz))
o.PutUint32(b[8*4:], uint32(s.Offset))
o.PutUint32(b[9*4:], uint32(s.Filesz))
o.PutUint32(b[10*4:], s.Maxprot)
o.PutUint32(b[11*4:], s.Prot)
o.PutUint32(b[12*4:], s.Nsect)
o.PutUint32(b[13*4:], uint32(s.Flag))
return 14 * 4
}
func (s *Segment) Put64(b []byte, o binary.ByteOrder) int {
o.PutUint32(b[0*4:], uint32(s.LoadCmd))
o.PutUint32(b[1*4:], s.Len)
putAtMost16Bytes(b[2*4:], s.Name)
o.PutUint64(b[6*4+0*8:], s.Addr)
o.PutUint64(b[6*4+1*8:], s.Memsz)
o.PutUint64(b[6*4+2*8:], s.Offset)
o.PutUint64(b[6*4+3*8:], s.Filesz)
o.PutUint32(b[6*4+4*8:], s.Maxprot)
o.PutUint32(b[7*4+4*8:], s.Prot)
o.PutUint32(b[8*4+4*8:], s.Nsect)
o.PutUint32(b[9*4+4*8:], uint32(s.Flag))
return 10*4 + 4*8
}
// LoadCmdBytes is a command-tagged sequence of bytes.
// This is used for Load Commands that are not (yet)
// interesting to us, and to common up this behavior for
// all those that are.
type LoadCmdBytes struct {
LoadCmd
LoadBytes
}
type SectionHeader struct {
Name string
Seg string
Addr uint64
Size uint64
Offset uint32
Align uint32
Reloff uint32
Nreloc uint32
Flags SecFlags
Reserved1 uint32
Reserved2 uint32
Reserved3 uint32 // only present if original was 64-bit
}
// A Reloc represents a Mach-O relocation.
type Reloc struct {
Addr uint32
Value uint32
// when Scattered == false && Extern == true, Value is the symbol number.
// when Scattered == false && Extern == false, Value is the section number.
// when Scattered == true, Value is the value that this reloc refers to.
Type uint8
Len uint8 // 0=byte, 1=word, 2=long, 3=quad
Pcrel bool
Extern bool // valid if Scattered == false
Scattered bool
}
type Section struct {
SectionHeader
Relocs []Reloc
// Embed ReaderAt for ReadAt method.
// Do not embed SectionReader directly
// to avoid having Read and Seek.
// If a client wants Read and Seek it must use
// Open() to avoid fighting over the seek offset
// with other clients.
io.ReaderAt
sr *io.SectionReader
}
func (s *Section) Put32(b []byte, o binary.ByteOrder) int {
putAtMost16Bytes(b[0:], s.Name)
putAtMost16Bytes(b[16:], s.Seg)
o.PutUint32(b[8*4:], uint32(s.Addr))
o.PutUint32(b[9*4:], uint32(s.Size))
o.PutUint32(b[10*4:], s.Offset)
o.PutUint32(b[11*4:], s.Align)
o.PutUint32(b[12*4:], s.Reloff)
o.PutUint32(b[13*4:], s.Nreloc)
o.PutUint32(b[14*4:], uint32(s.Flags))
o.PutUint32(b[15*4:], s.Reserved1)
o.PutUint32(b[16*4:], s.Reserved2)
a := 17 * 4
return a + s.PutRelocs(b[a:], o)
}
func (s *Section) Put64(b []byte, o binary.ByteOrder) int {
putAtMost16Bytes(b[0:], s.Name)
putAtMost16Bytes(b[16:], s.Seg)
o.PutUint64(b[8*4+0*8:], s.Addr)
o.PutUint64(b[8*4+1*8:], s.Size)
o.PutUint32(b[8*4+2*8:], s.Offset)
o.PutUint32(b[9*4+2*8:], s.Align)
o.PutUint32(b[10*4+2*8:], s.Reloff)
o.PutUint32(b[11*4+2*8:], s.Nreloc)
o.PutUint32(b[12*4+2*8:], uint32(s.Flags))
o.PutUint32(b[13*4+2*8:], s.Reserved1)
o.PutUint32(b[14*4+2*8:], s.Reserved2)
o.PutUint32(b[15*4+2*8:], s.Reserved3)
a := 16*4 + 2*8
return a + s.PutRelocs(b[a:], o)
}
func (s *Section) PutRelocs(b []byte, o binary.ByteOrder) int {
a := 0
for _, r := range s.Relocs {
var ri relocInfo
typ := uint32(r.Type) & (1<<4 - 1)
len := uint32(r.Len) & (1<<2 - 1)
pcrel := uint32(0)
if r.Pcrel {
pcrel = 1
}
ext := uint32(0)
if r.Extern {
ext = 1
}
switch {
case r.Scattered:
ri.Addr = r.Addr&(1<<24-1) | typ<<24 | len<<28 | 1<<31 | pcrel<<30
ri.Symnum = r.Value
case o == binary.LittleEndian:
ri.Addr = r.Addr
ri.Symnum = r.Value&(1<<24-1) | pcrel<<24 | len<<25 | ext<<27 | typ<<28
case o == binary.BigEndian:
ri.Addr = r.Addr
ri.Symnum = r.Value<<8 | pcrel<<7 | len<<5 | ext<<4 | typ
}
o.PutUint32(b, ri.Addr)
o.PutUint32(b[4:], ri.Symnum)
a += 8
b = b[8:]
}
return a
}
func putAtMost16Bytes(b []byte, n string) {
for i := range n { // at most 16 bytes
if i == 16 {
break
}
b[i] = n[i]
}
}
// A Symbol is a Mach-O 32-bit or 64-bit symbol table entry.
type Symbol struct {
Name string
Type uint8
Sect uint8
Desc uint16
Value uint64
}
/*
* Mach-O reader
*/
// FormatError is returned by some operations if the data does
// not have the correct format for an object file.
type FormatError struct {
off int64
msg string
}
func formatError(off int64, format string, data ...any) *FormatError {
return &FormatError{off, fmt.Sprintf(format, data...)}
}
func (e *FormatError) Error() string {
return e.msg + fmt.Sprintf(" in record at byte %#x", e.off)
}
func (e *FormatError) String() string {
return e.Error()
}
// DerivedCopy returns a modified copy of the TOC, with empty loads and sections,
// and with the specified header type and flags.
func (t *FileTOC) DerivedCopy(Type HdrType, Flags HdrFlags) *FileTOC {
h := t.FileHeader
h.NCommands, h.SizeCommands, h.Type, h.Flags = 0, 0, Type, Flags
return &FileTOC{FileHeader: h, ByteOrder: t.ByteOrder}
}
// TOCSize returns the size in bytes of the object file representation
// of the header and Load Commands (including Segments and Sections, but
// not their contents) at the beginning of a Mach-O file. This typically
// overlaps the text segment in the object file.
func (t *FileTOC) TOCSize() uint32 {
return t.HdrSize() + t.LoadSize()
}
// LoadAlign returns the required alignment of Load commands in a binary.
// This is used to add padding for necessary alignment.
func (t *FileTOC) LoadAlign() uint64 {
if t.Magic == Magic64 {
return 8
}
return 4
}
// SymbolSize returns the size in bytes of a Symbol (Nlist32 or Nlist64)
func (t *FileTOC) SymbolSize() uint32 {
if t.Magic == Magic64 {
return uint32(unsafe.Sizeof(Nlist64{}))
}
return uint32(unsafe.Sizeof(Nlist32{}))
}
// HdrSize returns the size in bytes of the Macho header for a given
// magic number (where the magic number has been appropriately byte-swapped).
func (t *FileTOC) HdrSize() uint32 {
switch t.Magic {
case Magic32:
return fileHeaderSize32
case Magic64:
return fileHeaderSize64
case MagicFat:
panic("MagicFat not handled yet")
default:
panic(fmt.Sprintf("Unexpected magic number 0x%x, expected Mach-O object file", t.Magic))
}
}
// LoadSize returns the size of all the load commands in a file's table-of contents
// (but not their associated data, e.g., sections and symbol tables)
func (t *FileTOC) LoadSize() uint32 {
cmdsz := uint32(0)
for _, l := range t.Loads {
s := l.LoadSize(t)
cmdsz += s
}
return cmdsz
}
// FileSize returns the size in bytes of the header, load commands, and the
// in-file contents of all the segments and sections included in those
// load commands, accounting for their offsets within the file.
func (t *FileTOC) FileSize() uint64 {
sz := uint64(t.LoadSize()) // ought to be contained in text segment, but just in case.
for _, l := range t.Loads {
if s, ok := l.(*Segment); ok {
if m := s.Offset + s.Filesz; m > sz {
sz = m
}
}
}
return sz
}
// Put writes the header and all load commands to buffer, using
// the byte ordering specified in FileTOC t. For sections, this
// writes the headers that come in-line with the segment Load commands,
// but does not write the reference data for those sections.
func (t *FileTOC) Put(buffer []byte) int {
next := t.FileHeader.Put(buffer, t.ByteOrder)
for _, l := range t.Loads {
if s, ok := l.(*Segment); ok {
switch t.Magic {
case Magic64:
next += s.Put64(buffer[next:], t.ByteOrder)
for i := uint32(0); i < s.Nsect; i++ {
c := t.Sections[i+s.Firstsect]
next += c.Put64(buffer[next:], t.ByteOrder)
}
case Magic32:
next += s.Put32(buffer[next:], t.ByteOrder)
for i := uint32(0); i < s.Nsect; i++ {
c := t.Sections[i+s.Firstsect]
next += c.Put32(buffer[next:], t.ByteOrder)
}
default:
panic(fmt.Sprintf("Unexpected magic number 0x%x", t.Magic))
}
} else {
next += l.Put(buffer[next:], t.ByteOrder)
}
}
return next
}
// UncompressedSize returns the size of the segment with its sections uncompressed, ignoring
// its offset within the file. The returned size is rounded up to the power of two in align.
func (s *Segment) UncompressedSize(t *FileTOC, align uint64) uint64 {
sz := uint64(0)
for j := uint32(0); j < s.Nsect; j++ {
c := t.Sections[j+s.Firstsect]
sz += c.UncompressedSize()
}
return (sz + align - 1) & uint64(-int64(align))
}
func (s *Section) UncompressedSize() uint64 {
if !strings.HasPrefix(s.Name, "__z") {
return s.Size
}
b := make([]byte, 12)
n, err := s.sr.ReadAt(b, 0)
if err != nil {
panic("Malformed object file")
}
if n != len(b) {
return s.Size
}
if string(b[:4]) == "ZLIB" {
return binary.BigEndian.Uint64(b[4:12])
}
return s.Size
}
func (s *Section) PutData(b []byte) {
bb := b[0:s.Size]
n, err := s.sr.ReadAt(bb, 0)
if err != nil || uint64(n) != s.Size {
panic("Malformed object file (ReadAt error)")
}
}
func (s *Section) PutUncompressedData(b []byte) {
if strings.HasPrefix(s.Name, "__z") {
bb := make([]byte, 12)
n, err := s.sr.ReadAt(bb, 0)
if err != nil {
panic("Malformed object file")
}
if n == len(bb) && string(bb[:4]) == "ZLIB" {
size := binary.BigEndian.Uint64(bb[4:12])
// Decompress starting at b[12:]
r, err := zlib.NewReader(io.NewSectionReader(s, 12, int64(size)-12))
if err != nil {
panic("Malformed object file (zlib.NewReader error)")
}
n, err := io.ReadFull(r, b[0:size])
if err != nil {
panic("Malformed object file (ReadFull error)")
}
if uint64(n) != size {
panic(fmt.Sprintf("PutUncompressedData, expected to read %d bytes, instead read %d", size, n))
}
if err := r.Close(); err != nil {
panic("Malformed object file (Close error)")
}
return
}
}
// Not compressed
s.PutData(b)
}
func (b LoadBytes) String() string {
var s strings.Builder
s.WriteString("[")
for i, a := range b {
if i > 0 {
s.WriteString(" ")
if len(b) > 48 && i >= 16 {
fmt.Fprintf(&s, "... (%d bytes)", len(b))
break
}
}
fmt.Fprintf(&s, "%x", a)
}
s.WriteString("]")
return s.String()
}
func (b LoadBytes) Raw() []byte { return b }
func (b LoadBytes) Copy() LoadBytes { return LoadBytes(slices.Clone(b)) }
func (b LoadBytes) LoadSize(t *FileTOC) uint32 { return uint32(len(b)) }
func (lc LoadCmd) Put(b []byte, o binary.ByteOrder) int {
panic(fmt.Sprintf("Put not implemented for %s", lc.String()))
}
func (s LoadCmdBytes) String() string {
return s.LoadCmd.String() + ": " + s.LoadBytes.String()
}
func (s LoadCmdBytes) Copy() LoadCmdBytes {
return LoadCmdBytes{LoadCmd: s.LoadCmd, LoadBytes: s.LoadBytes.Copy()}
}
func (s *SegmentHeader) String() string {
return fmt.Sprintf(
"Seg %s, len=0x%x, addr=0x%x, memsz=0x%x, offset=0x%x, filesz=0x%x, maxprot=0x%x, prot=0x%x, nsect=%d, flag=0x%x, firstsect=%d",
s.Name, s.Len, s.Addr, s.Memsz, s.Offset, s.Filesz, s.Maxprot, s.Prot, s.Nsect, s.Flag, s.Firstsect)
}
func (s *Segment) String() string {
return fmt.Sprintf(
"Seg %s, len=0x%x, addr=0x%x, memsz=0x%x, offset=0x%x, filesz=0x%x, maxprot=0x%x, prot=0x%x, nsect=%d, flag=0x%x, firstsect=%d",
s.Name, s.Len, s.Addr, s.Memsz, s.Offset, s.Filesz, s.Maxprot, s.Prot, s.Nsect, s.Flag, s.Firstsect)
}
// Data reads and returns the contents of the segment.
func (s *Segment) Data() ([]byte, error) {
dat := make([]byte, s.sr.Size())
n, err := s.sr.ReadAt(dat, 0)
if n == len(dat) {
err = nil
}
return dat[0:n], err
}
func (s *Segment) Copy() *Segment {
r := &Segment{SegmentHeader: s.SegmentHeader}
return r
}
func (s *Segment) CopyZeroed() *Segment {
r := s.Copy()
r.Filesz = 0
r.Offset = 0
r.Nsect = 0
r.Firstsect = 0
if s.Command() == LcSegment64 {
r.Len = uint32(unsafe.Sizeof(Segment64{}))
} else {
r.Len = uint32(unsafe.Sizeof(Segment32{}))
}
return r
}
func (s *Segment) LoadSize(t *FileTOC) uint32 {
if s.Command() == LcSegment64 {
return uint32(unsafe.Sizeof(Segment64{})) + uint32(s.Nsect)*uint32(unsafe.Sizeof(Section64{}))
}
return uint32(unsafe.Sizeof(Segment32{})) + uint32(s.Nsect)*uint32(unsafe.Sizeof(Section32{}))
}
// Open returns a new ReadSeeker reading the segment.
func (s *Segment) Open() io.ReadSeeker { return io.NewSectionReader(s.sr, 0, 1<<63-1) }
// Data reads and returns the contents of the Mach-O section.
func (s *Section) Data() ([]byte, error) {
dat := make([]byte, s.sr.Size())
n, err := s.sr.ReadAt(dat, 0)
if n == len(dat) {
err = nil
}
return dat[0:n], err
}
func (s *Section) Copy() *Section {
return &Section{SectionHeader: s.SectionHeader}
}
// Open returns a new ReadSeeker reading the Mach-O section.
func (s *Section) Open() io.ReadSeeker { return io.NewSectionReader(s.sr, 0, 1<<63-1) }
// A Dylib represents a Mach-O load dynamic library command.
type Dylib struct {
DylibCmd
Name string
Time uint32
CurrentVersion uint32
CompatVersion uint32
}
func (s *Dylib) String() string { return "Dylib " + s.Name }
func (s *Dylib) Copy() *Dylib {
r := *s
return &r
}
func (s *Dylib) LoadSize(t *FileTOC) uint32 {
return uint32(RoundUp(uint64(unsafe.Sizeof(DylibCmd{}))+uint64(len(s.Name)), t.LoadAlign()))
}
type Dylinker struct {
DylinkerCmd // shared by 3 commands, need the LoadCmd
Name string
}
func (s *Dylinker) String() string { return s.DylinkerCmd.LoadCmd.String() + " " + s.Name }
func (s *Dylinker) Copy() *Dylinker {
return &Dylinker{DylinkerCmd: s.DylinkerCmd, Name: s.Name}
}
func (s *Dylinker) LoadSize(t *FileTOC) uint32 {
return uint32(RoundUp(uint64(unsafe.Sizeof(DylinkerCmd{}))+uint64(len(s.Name)), t.LoadAlign()))
}
// A Symtab represents a Mach-O symbol table command.
type Symtab struct {
SymtabCmd
Syms []Symbol
}
func (s *Symtab) Put(b []byte, o binary.ByteOrder) int {
o.PutUint32(b[0*4:], uint32(s.LoadCmd))
o.PutUint32(b[1*4:], s.Len)
o.PutUint32(b[2*4:], s.Symoff)
o.PutUint32(b[3*4:], s.Nsyms)
o.PutUint32(b[4*4:], s.Stroff)
o.PutUint32(b[5*4:], s.Strsize)
return 6 * 4
}
func (s *Symtab) String() string { return fmt.Sprintf("Symtab %#v", s.SymtabCmd) }
func (s *Symtab) Copy() *Symtab {
return &Symtab{SymtabCmd: s.SymtabCmd, Syms: slices.Clone(s.Syms)}
}
func (s *Symtab) LoadSize(t *FileTOC) uint32 {
return uint32(unsafe.Sizeof(SymtabCmd{}))
}
type LinkEditData struct {
LinkEditDataCmd
}
func (s *LinkEditData) String() string { return "LinkEditData " + s.LoadCmd.String() }
func (s *LinkEditData) Copy() *LinkEditData {
return &LinkEditData{LinkEditDataCmd: s.LinkEditDataCmd}
}
func (s *LinkEditData) LoadSize(t *FileTOC) uint32 {
return uint32(unsafe.Sizeof(LinkEditDataCmd{}))
}
type Uuid struct {
UuidCmd
}
func (s *Uuid) String() string {
return fmt.Sprintf("Uuid %X-%X-%X-%X-%X",
s.Id[0:4], s.Id[4:6], s.Id[6:8], s.Id[8:10], s.Id[10:16])
} // 8-4-4-4-12
func (s *Uuid) Copy() *Uuid {
return &Uuid{UuidCmd: s.UuidCmd}
}
func (s *Uuid) LoadSize(t *FileTOC) uint32 {
return uint32(unsafe.Sizeof(UuidCmd{}))
}
func (s *Uuid) Put(b []byte, o binary.ByteOrder) int {
o.PutUint32(b[0*4:], uint32(s.LoadCmd))
o.PutUint32(b[1*4:], s.Len)
copy(b[2*4:], s.Id[0:])
return int(s.Len)
}
type DyldInfo struct {
DyldInfoCmd
}
func (s *DyldInfo) String() string { return "DyldInfo " + s.LoadCmd.String() }
func (s *DyldInfo) Copy() *DyldInfo {
return &DyldInfo{DyldInfoCmd: s.DyldInfoCmd}
}
func (s *DyldInfo) LoadSize(t *FileTOC) uint32 {
return uint32(unsafe.Sizeof(DyldInfoCmd{}))
}
type EncryptionInfo struct {
EncryptionInfoCmd
}
func (s *EncryptionInfo) String() string { return "EncryptionInfo " + s.LoadCmd.String() }
func (s *EncryptionInfo) Copy() *EncryptionInfo {
return &EncryptionInfo{EncryptionInfoCmd: s.EncryptionInfoCmd}
}
func (s *EncryptionInfo) LoadSize(t *FileTOC) uint32 {
return uint32(unsafe.Sizeof(EncryptionInfoCmd{}))
}
// A Dysymtab represents a Mach-O dynamic symbol table command.
type Dysymtab struct {
DysymtabCmd
IndirectSyms []uint32 // indices into Symtab.Syms
}
func (s *Dysymtab) String() string { return fmt.Sprintf("Dysymtab %#v", s.DysymtabCmd) }
func (s *Dysymtab) Copy() *Dysymtab {
return &Dysymtab{DysymtabCmd: s.DysymtabCmd, IndirectSyms: slices.Clone(s.IndirectSyms)}
}
func (s *Dysymtab) LoadSize(t *FileTOC) uint32 {
return uint32(unsafe.Sizeof(DysymtabCmd{}))
}
// A Rpath represents a Mach-O rpath command.
type Rpath struct {
LoadCmd
Path string
}
func (s *Rpath) String() string { return "Rpath " + s.Path }
func (s *Rpath) Command() LoadCmd { return LcRpath }
func (s *Rpath) Copy() *Rpath {
return &Rpath{Path: s.Path}
}
func (s *Rpath) LoadSize(t *FileTOC) uint32 {
return uint32(RoundUp(uint64(unsafe.Sizeof(RpathCmd{}))+uint64(len(s.Path)), t.LoadAlign()))
}
// Open opens the named file using os.Open and prepares it for use as a Mach-O binary.
func Open(name string) (*File, error) {
f, err := os.Open(name)
if err != nil {
return nil, err
}
ff, err := NewFile(f)
if err != nil {
f.Close()
return nil, err
}
ff.closer = f
return ff, nil
}
// Close closes the File.
// If the File was created using NewFile directly instead of Open,
// Close has no effect.
func (f *File) Close() error {
var err error
if f.closer != nil {
err = f.closer.Close()
f.closer = nil
}
return err
}
// NewFile creates a new File for accessing a Mach-O binary in an underlying reader.
// The Mach-O binary is expected to start at position 0 in the ReaderAt.
func NewFile(r io.ReaderAt) (*File, error) {
f := new(File)
sr := io.NewSectionReader(r, 0, 1<<63-1)
// Read and decode Mach magic to determine byte order, size.
// Magic32 and Magic64 differ only in the bottom bit.
var ident [4]byte
if _, err := r.ReadAt(ident[0:], 0); err != nil {
return nil, err
}
be := binary.BigEndian.Uint32(ident[0:])
le := binary.LittleEndian.Uint32(ident[0:])
switch Magic32 &^ 1 {
case be &^ 1:
f.ByteOrder = binary.BigEndian
f.Magic = be
case le &^ 1:
f.ByteOrder = binary.LittleEndian
f.Magic = le
default:
return nil, formatError(0, "invalid magic number be=0x%x, le=0x%x", be, le)
}
// Read entire file header.
if err := binary.Read(sr, f.ByteOrder, &f.FileHeader); err != nil {
return nil, err
}
// Then load commands.
offset := int64(fileHeaderSize32)
if f.Magic == Magic64 {
offset = fileHeaderSize64
}
dat := make([]byte, f.SizeCommands)
if _, err := r.ReadAt(dat, offset); err != nil {
return nil, err
}
f.Loads = make([]Load, f.NCommands)
bo := f.ByteOrder
for i := range f.Loads {
// Each load command begins with uint32 command and length.
if len(dat) < 8 {
return nil, formatError(offset, "command block too small, len(dat) = %d", len(dat))
}
cmd, siz := LoadCmd(bo.Uint32(dat[0:4])), bo.Uint32(dat[4:8])
if siz < 8 || siz > uint32(len(dat)) {
return nil, formatError(offset, "invalid command block size, len(dat)=%d, size=%d", len(dat), siz)
}
var cmddat []byte
cmddat, dat = dat[0:siz], dat[siz:]
offset += int64(siz)
var s *Segment
switch cmd {
default:
f.Loads[i] = LoadCmdBytes{LoadCmd(cmd), LoadBytes(cmddat)}
case LcUuid:
var hdr UuidCmd
b := bytes.NewReader(cmddat)
if err := binary.Read(b, bo, &hdr); err != nil {
return nil, err
}
l := &Uuid{UuidCmd: hdr}
f.Loads[i] = l
case LcRpath:
var hdr RpathCmd
b := bytes.NewReader(cmddat)
if err := binary.Read(b, bo, &hdr); err != nil {
return nil, err
}
l := &Rpath{LoadCmd: hdr.LoadCmd}
if hdr.Path >= uint32(len(cmddat)) {
return nil, formatError(offset, "invalid path in rpath command, len(cmddat)=%d, hdr.Path=%d", len(cmddat), hdr.Path)
}
l.Path = cstring(cmddat[hdr.Path:])
f.Loads[i] = l
case LcLoadDylinker, LcIdDylinker, LcDyldEnvironment:
var hdr DylinkerCmd
b := bytes.NewReader(cmddat)
if err := binary.Read(b, bo, &hdr); err != nil {
return nil, err
}
l := new(Dylinker)
if hdr.Name >= uint32(len(cmddat)) {
return nil, formatError(offset, "invalid name in dynamic linker command, hdr.Name=%d, len(cmddat)=%d", hdr.Name, len(cmddat))
}
l.Name = cstring(cmddat[hdr.Name:])
l.DylinkerCmd = hdr
f.Loads[i] = l
case LcDylib:
var hdr DylibCmd
b := bytes.NewReader(cmddat)
if err := binary.Read(b, bo, &hdr); err != nil {
return nil, err
}
l := new(Dylib)
if hdr.Name >= uint32(len(cmddat)) {
return nil, formatError(offset, "invalid name in dynamic library command, hdr.Name=%d, len(cmddat)=%d", hdr.Name, len(cmddat))
}
l.Name = cstring(cmddat[hdr.Name:])
l.Time = hdr.Time
l.CurrentVersion = hdr.CurrentVersion
l.CompatVersion = hdr.CompatVersion
f.Loads[i] = l
case LcSymtab:
var hdr SymtabCmd
b := bytes.NewReader(cmddat)
if err := binary.Read(b, bo, &hdr); err != nil {
return nil, err
}
strtab := make([]byte, hdr.Strsize)
if _, err := r.ReadAt(strtab, int64(hdr.Stroff)); err != nil {
return nil, err
}
var symsz int
if f.Magic == Magic64 {
symsz = 16
} else {
symsz = 12
}
symdat := make([]byte, int(hdr.Nsyms)*symsz)
if _, err := r.ReadAt(symdat, int64(hdr.Symoff)); err != nil {
return nil, err
}
st, err := f.parseSymtab(symdat, strtab, &hdr, offset)
st.SymtabCmd = hdr
if err != nil {
return nil, err
}
f.Loads[i] = st
f.Symtab = st
case LcDysymtab:
var hdr DysymtabCmd
b := bytes.NewReader(cmddat)
if err := binary.Read(b, bo, &hdr); err != nil {
return nil, err
}
dat := make([]byte, hdr.Nindirectsyms*4)
if _, err := r.ReadAt(dat, int64(hdr.Indirectsymoff)); err != nil {
return nil, err
}
x := make([]uint32, hdr.Nindirectsyms)
if err := binary.Read(bytes.NewReader(dat), bo, x); err != nil {
return nil, err
}
st := new(Dysymtab)
st.DysymtabCmd = hdr
st.IndirectSyms = x
f.Loads[i] = st
f.Dysymtab = st
case LcSegment:
var seg32 Segment32
b := bytes.NewReader(cmddat)
if err := binary.Read(b, bo, &seg32); err != nil {
return nil, err
}
s = new(Segment)
s.LoadCmd = cmd
s.Len = siz
s.Name = cstring(seg32.Name[0:])
s.Addr = uint64(seg32.Addr)
s.Memsz = uint64(seg32.Memsz)
s.Offset = uint64(seg32.Offset)
s.Filesz = uint64(seg32.Filesz)
s.Maxprot = seg32.Maxprot
s.Prot = seg32.Prot
s.Nsect = seg32.Nsect
s.Flag = seg32.Flag
s.Firstsect = uint32(len(f.Sections))
f.Loads[i] = s
for i := 0; i < int(s.Nsect); i++ {
var sh32 Section32
if err := binary.Read(b, bo, &sh32); err != nil {
return nil, err
}
sh := new(Section)
sh.Name = cstring(sh32.Name[0:])
sh.Seg = cstring(sh32.Seg[0:])
sh.Addr = uint64(sh32.Addr)
sh.Size = uint64(sh32.Size)
sh.Offset = sh32.Offset
sh.Align = sh32.Align
sh.Reloff = sh32.Reloff
sh.Nreloc = sh32.Nreloc
sh.Flags = sh32.Flags
sh.Reserved1 = sh32.Reserve1
sh.Reserved2 = sh32.Reserve2
if err := f.pushSection(sh, r); err != nil {
return nil, err
}
}
case LcSegment64:
var seg64 Segment64
b := bytes.NewReader(cmddat)
if err := binary.Read(b, bo, &seg64); err != nil {
return nil, err
}
s = new(Segment)
s.LoadCmd = cmd
s.Len = siz
s.Name = cstring(seg64.Name[0:])
s.Addr = seg64.Addr
s.Memsz = seg64.Memsz
s.Offset = seg64.Offset
s.Filesz = seg64.Filesz
s.Maxprot = seg64.Maxprot
s.Prot = seg64.Prot
s.Nsect = seg64.Nsect
s.Flag = seg64.Flag
s.Firstsect = uint32(len(f.Sections))
f.Loads[i] = s
for i := 0; i < int(s.Nsect); i++ {
var sh64 Section64
if err := binary.Read(b, bo, &sh64); err != nil {
return nil, err
}
sh := new(Section)
sh.Name = cstring(sh64.Name[0:])
sh.Seg = cstring(sh64.Seg[0:])
sh.Addr = sh64.Addr
sh.Size = sh64.Size
sh.Offset = sh64.Offset
sh.Align = sh64.Align
sh.Reloff = sh64.Reloff
sh.Nreloc = sh64.Nreloc
sh.Flags = sh64.Flags
sh.Reserved1 = sh64.Reserve1
sh.Reserved2 = sh64.Reserve2
sh.Reserved3 = sh64.Reserve3
if err := f.pushSection(sh, r); err != nil {
return nil, err
}
}
case LcCodeSignature, LcSegmentSplitInfo, LcFunctionStarts,
LcDataInCode, LcDylibCodeSignDrs:
var hdr LinkEditDataCmd
b := bytes.NewReader(cmddat)
if err := binary.Read(b, bo, &hdr); err != nil {
return nil, err
}
l := new(LinkEditData)
l.LinkEditDataCmd = hdr
f.Loads[i] = l
case LcEncryptionInfo, LcEncryptionInfo64:
var hdr EncryptionInfoCmd
b := bytes.NewReader(cmddat)
if err := binary.Read(b, bo, &hdr); err != nil {
return nil, err
}
l := new(EncryptionInfo)
l.EncryptionInfoCmd = hdr
f.Loads[i] = l
case LcDyldInfo, LcDyldInfoOnly:
var hdr DyldInfoCmd
b := bytes.NewReader(cmddat)
if err := binary.Read(b, bo, &hdr); err != nil {
return nil, err
}
l := new(DyldInfo)
l.DyldInfoCmd = hdr
f.Loads[i] = l
}
if s != nil {
s.sr = io.NewSectionReader(r, int64(s.Offset), int64(s.Filesz))
s.ReaderAt = s.sr
}
if f.Loads[i].LoadSize(&f.FileTOC) != siz {
fmt.Printf("Oops, actual size was %d, calculated was %d, load was %s\n", siz, f.Loads[i].LoadSize(&f.FileTOC), f.Loads[i].String())
panic("oops")
}
}
return f, nil
}
func (f *File) parseSymtab(symdat, strtab []byte, hdr *SymtabCmd, offset int64) (*Symtab, error) {
bo := f.ByteOrder
symtab := make([]Symbol, hdr.Nsyms)
b := bytes.NewReader(symdat)
for i := range symtab {
var n Nlist64
if f.Magic == Magic64 {
if err := binary.Read(b, bo, &n); err != nil {
return nil, err
}
} else {
var n32 Nlist32
if err := binary.Read(b, bo, &n32); err != nil {
return nil, err
}
n.Name = n32.Name
n.Type = n32.Type
n.Sect = n32.Sect
n.Desc = n32.Desc
n.Value = uint64(n32.Value)
}
sym := &symtab[i]
if n.Name >= uint32(len(strtab)) {
return nil, formatError(offset, "invalid name in symbol table, n.Name=%d, len(strtab)=%d", n.Name, len(strtab))
}
sym.Name = cstring(strtab[n.Name:])
sym.Type = n.Type
sym.Sect = n.Sect
sym.Desc = n.Desc
sym.Value = n.Value
}
st := new(Symtab)
st.Syms = symtab
return st, nil
}
type relocInfo struct {
Addr uint32
Symnum uint32
}
func (f *File) pushSection(sh *Section, r io.ReaderAt) error {
f.Sections = append(f.Sections, sh)
sh.sr = io.NewSectionReader(r, int64(sh.Offset), int64(sh.Size))
sh.ReaderAt = sh.sr
if sh.Nreloc > 0 {
reldat := make([]byte, int(sh.Nreloc)*8)
if _, err := r.ReadAt(reldat, int64(sh.Reloff)); err != nil {
return err
}
b := bytes.NewReader(reldat)
bo := f.ByteOrder
sh.Relocs = make([]Reloc, sh.Nreloc)
for i := range sh.Relocs {
rel := &sh.Relocs[i]
var ri relocInfo
if err := binary.Read(b, bo, &ri); err != nil {
return err
}
if ri.Addr&(1<<31) != 0 { // scattered
rel.Addr = ri.Addr & (1<<24 - 1)
rel.Type = uint8((ri.Addr >> 24) & (1<<4 - 1))
rel.Len = uint8((ri.Addr >> 28) & (1<<2 - 1))
rel.Pcrel = ri.Addr&(1<<30) != 0
rel.Value = ri.Symnum
rel.Scattered = true
} else {
switch bo {
case binary.LittleEndian:
rel.Addr = ri.Addr
rel.Value = ri.Symnum & (1<<24 - 1)
rel.Pcrel = ri.Symnum&(1<<24) != 0
rel.Len = uint8((ri.Symnum >> 25) & (1<<2 - 1))
rel.Extern = ri.Symnum&(1<<27) != 0
rel.Type = uint8((ri.Symnum >> 28) & (1<<4 - 1))
case binary.BigEndian:
rel.Addr = ri.Addr
rel.Value = ri.Symnum >> 8
rel.Pcrel = ri.Symnum&(1<<7) != 0
rel.Len = uint8((ri.Symnum >> 5) & (1<<2 - 1))
rel.Extern = ri.Symnum&(1<<4) != 0
rel.Type = uint8(ri.Symnum & (1<<4 - 1))
default:
panic("unreachable")
}
}
}
}
return nil
}
func cstring(b []byte) string {
i := bytes.IndexByte(b, 0)
if i == -1 {
i = len(b)
}
return string(b[0:i])
}
// Segment returns the first Segment with the given name, or nil if no such segment exists.
func (f *File) Segment(name string) *Segment {
for _, l := range f.Loads {
if s, ok := l.(*Segment); ok && s.Name == name {
return s
}
}
return nil
}
// Section returns the first section with the given name, or nil if no such
// section exists.
func (f *File) Section(name string) *Section {
for _, s := range f.Sections {
if s.Name == name {
return s
}
}
return nil
}
// DWARF returns the DWARF debug information for the Mach-O file.
func (f *File) DWARF() (*dwarf.Data, error) {
dwarfSuffix := func(s *Section) string {
switch {
case strings.HasPrefix(s.Name, "__debug_"):
return s.Name[8:]
case strings.HasPrefix(s.Name, "__zdebug_"):
return s.Name[9:]
default:
return ""
}
}
sectionData := func(s *Section) ([]byte, error) {
b, err := s.Data()
if err != nil && uint64(len(b)) < s.Size {
return nil, err
}
if len(b) >= 12 && string(b[:4]) == "ZLIB" {
dlen := binary.BigEndian.Uint64(b[4:12])
dbuf := make([]byte, dlen)
r, err := zlib.NewReader(bytes.NewBuffer(b[12:]))
if err != nil {
return nil, err
}
if _, err := io.ReadFull(r, dbuf); err != nil {
return nil, err
}
if err := r.Close(); err != nil {
return nil, err
}
b = dbuf
}
return b, nil
}
// There are many other DWARF sections, but these
// are the ones the debug/dwarf package uses.
// Don't bother loading others.
var dat = map[string][]byte{"abbrev": nil, "info": nil, "str": nil, "line": nil, "ranges": nil}
for _, s := range f.Sections {
suffix := dwarfSuffix(s)
if suffix == "" {
continue
}
if _, ok := dat[suffix]; !ok {
continue
}
b, err := sectionData(s)
if err != nil {
return nil, err
}
dat[suffix] = b
}
d, err := dwarf.New(dat["abbrev"], nil, nil, dat["info"], dat["line"], nil, dat["ranges"], dat["str"])
if err != nil {
return nil, err
}
// Look for DWARF4 .debug_types sections.
for i, s := range f.Sections {
suffix := dwarfSuffix(s)
if suffix != "types" {
continue
}
b, err := sectionData(s)
if err != nil {
return nil, err
}
err = d.AddTypes(fmt.Sprintf("types-%d", i), b)
if err != nil {
return nil, err
}
}
return d, nil
}
// ImportedSymbols returns the names of all symbols
// referred to by the binary f that are expected to be
// satisfied by other libraries at dynamic load time.
func (f *File) ImportedSymbols() ([]string, error) {
if f.Dysymtab == nil || f.Symtab == nil {
return nil, formatError(0, "missing symbol table, f.Dsymtab=%v, f.Symtab=%v", f.Dysymtab, f.Symtab)
}
st := f.Symtab
dt := f.Dysymtab
var all []string
for _, s := range st.Syms[dt.Iundefsym : dt.Iundefsym+dt.Nundefsym] {
all = append(all, s.Name)
}
return all, nil
}
// ImportedLibraries returns the paths of all libraries
// referred to by the binary f that are expected to be
// linked with the binary at dynamic link time.
func (f *File) ImportedLibraries() ([]string, error) {
var all []string
for _, l := range f.Loads {
if lib, ok := l.(*Dylib); ok {
all = append(all, lib.Name)
}
}
return all, nil
}
func RoundUp(x, align uint64) uint64 {
return uint64((x + align - 1) & -align)
}
================================================
FILE: cmd/splitdwarf/internal/macho/file_test.go
================================================
// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package macho
import (
"reflect"
"strings"
"testing"
)
type fileTest struct {
file string
hdr FileHeader
loads []any
sections []*SectionHeader
relocations map[string][]Reloc
}
var fileTests = []fileTest{
{
"testdata/gcc-386-darwin-exec",
FileHeader{0xfeedface, Cpu386, 0x3, 0x2, 0xc, 0x3c0, 0x85},
[]any{
&SegmentHeader{LcSegment, 0x38, "__PAGEZERO", 0x0, 0x1000, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0},
&SegmentHeader{LcSegment, 0xc0, "__TEXT", 0x1000, 0x1000, 0x0, 0x1000, 0x7, 0x5, 0x2, 0x0, 0},
&SegmentHeader{LcSegment, 0xc0, "__DATA", 0x2000, 0x1000, 0x1000, 0x1000, 0x7, 0x3, 0x2, 0x0, 2},
&SegmentHeader{LcSegment, 0x7c, "__IMPORT", 0x3000, 0x1000, 0x2000, 0x1000, 0x7, 0x7, 0x1, 0x0, 4},
&SegmentHeader{LcSegment, 0x38, "__LINKEDIT", 0x4000, 0x1000, 0x3000, 0x12c, 0x7, 0x1, 0x0, 0x0, 5},
nil, // LC_SYMTAB
nil, // LC_DYSYMTAB
nil, // LC_LOAD_DYLINKER
nil, // LC_UUID
nil, // LC_UNIXTHREAD
&Dylib{DylibCmd{}, "/usr/lib/libgcc_s.1.dylib", 0x2, 0x10000, 0x10000},
&Dylib{DylibCmd{}, "/usr/lib/libSystem.B.dylib", 0x2, 0x6f0104, 0x10000},
},
[]*SectionHeader{
{"__text", "__TEXT", 0x1f68, 0x88, 0xf68, 0x2, 0x0, 0x0, 0x80000400, 0, 0, 0},
{"__cstring", "__TEXT", 0x1ff0, 0xd, 0xff0, 0x0, 0x0, 0x0, 0x2, 0, 0, 0},
{"__data", "__DATA", 0x2000, 0x14, 0x1000, 0x2, 0x0, 0x0, 0x0, 0, 0, 0},
{"__dyld", "__DATA", 0x2014, 0x1c, 0x1014, 0x2, 0x0, 0x0, 0x0, 0, 0, 0},
{"__jump_table", "__IMPORT", 0x3000, 0xa, 0x2000, 0x6, 0x0, 0x0, 0x4000008, 0, 5, 0},
},
nil,
},
{
"testdata/gcc-amd64-darwin-exec",
FileHeader{0xfeedfacf, CpuAmd64, 0x80000003, 0x2, 0xb, 0x568, 0x85},
[]any{
&SegmentHeader{LcSegment64, 0x48, "__PAGEZERO", 0x0, 0x100000000, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0},
&SegmentHeader{LcSegment64, 0x1d8, "__TEXT", 0x100000000, 0x1000, 0x0, 0x1000, 0x7, 0x5, 0x5, 0x0, 0},
&SegmentHeader{LcSegment64, 0x138, "__DATA", 0x100001000, 0x1000, 0x1000, 0x1000, 0x7, 0x3, 0x3, 0x0, 5},
&SegmentHeader{LcSegment64, 0x48, "__LINKEDIT", 0x100002000, 0x1000, 0x2000, 0x140, 0x7, 0x1, 0x0, 0x0, 8},
nil, // LC_SYMTAB
nil, // LC_DYSYMTAB
nil, // LC_LOAD_DYLINKER
nil, // LC_UUID
nil, // LC_UNIXTHREAD
&Dylib{DylibCmd{}, "/usr/lib/libgcc_s.1.dylib", 0x2, 0x10000, 0x10000},
&Dylib{DylibCmd{}, "/usr/lib/libSystem.B.dylib", 0x2, 0x6f0104, 0x10000},
},
[]*SectionHeader{
{"__text", "__TEXT", 0x100000f14, 0x6d, 0xf14, 0x2, 0x0, 0x0, 0x80000400, 0, 0, 0},
{"__symbol_stub1", "__TEXT", 0x100000f81, 0xc, 0xf81, 0x0, 0x0, 0x0, 0x80000408, 0, 6, 0},
{"__stub_helper", "__TEXT", 0x100000f90, 0x18, 0xf90, 0x2, 0x0, 0x0, 0x0, 0, 0, 0},
{"__cstring", "__TEXT", 0x100000fa8, 0xd, 0xfa8, 0x0, 0x0, 0x0, 0x2, 0, 0, 0},
{"__eh_frame", "__TEXT", 0x100000fb8, 0x48, 0xfb8, 0x3, 0x0, 0x0, 0x6000000b, 0, 0, 0},
{"__data", "__DATA", 0x100001000, 0x1c, 0x1000, 0x3, 0x0, 0x0, 0x0, 0, 0, 0},
{"__dyld", "__DATA", 0x100001020, 0x38, 0x1020, 0x3, 0x0, 0x0, 0x0, 0, 0, 0},
{"__la_symbol_ptr", "__DATA", 0x100001058, 0x10, 0x1058, 0x2, 0x0, 0x0, 0x7, 2, 0, 0},
},
nil,
},
{
"testdata/gcc-amd64-darwin-exec-debug",
FileHeader{0xfeedfacf, CpuAmd64, 0x80000003, 0xa, 0x4, 0x5a0, 0},
[]any{
nil, // LC_UUID
&SegmentHeader{LcSegment64, 0x1d8, "__TEXT", 0x100000000, 0x1000, 0x0, 0x0, 0x7, 0x5, 0x5, 0x0, 0},
&SegmentHeader{LcSegment64, 0x138, "__DATA", 0x100001000, 0x1000, 0x0, 0x0, 0x7, 0x3, 0x3, 0x0, 5},
&SegmentHeader{LcSegment64, 0x278, "__DWARF", 0x100002000, 0x1000, 0x1000, 0x1bc, 0x7, 0x3, 0x7, 0x0, 8},
},
[]*SectionHeader{
{"__text", "__TEXT", 0x100000f14, 0x0, 0x0, 0x2, 0x0, 0x0, 0x80000400, 0, 0, 0},
{"__symbol_stub1", "__TEXT", 0x100000f81, 0x0, 0x0, 0x0, 0x0, 0x0, 0x80000408, 0, 6, 0},
{"__stub_helper", "__TEXT", 0x100000f90, 0x0, 0x0, 0x2, 0x0, 0x0, 0x0, 0, 0, 0},
{"__cstring", "__TEXT", 0x100000fa8, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0, 0, 0},
{"__eh_frame", "__TEXT", 0x100000fb8, 0x0, 0x0, 0x3, 0x0, 0x0, 0x6000000b, 0, 0, 0},
{"__data", "__DATA", 0x100001000, 0x0, 0x0, 0x3, 0x0, 0x0, 0x0, 0, 0, 0},
{"__dyld", "__DATA", 0x100001020, 0x0, 0x0, 0x3, 0x0, 0x0, 0x0, 0, 0, 0},
{"__la_symbol_ptr", "__DATA", 0x100001058, 0x0, 0x0, 0x2, 0x0, 0x0, 0x7, 2, 0, 0},
{"__debug_abbrev", "__DWARF", 0x100002000, 0x36, 0x1000, 0x0, 0x0, 0x0, 0x0, 0, 0, 0},
{"__debug_aranges", "__DWARF", 0x100002036, 0x30, 0x1036, 0x0, 0x0, 0x0, 0x0, 0, 0, 0},
{"__debug_frame", "__DWARF", 0x100002066, 0x40, 0x1066, 0x0, 0x0, 0x0, 0x0, 0, 0, 0},
{"__debug_info", "__DWARF", 0x1000020a6, 0x54, 0x10a6, 0x0, 0x0, 0x0, 0x0, 0, 0, 0},
{"__debug_line", "__DWARF", 0x1000020fa, 0x47, 0x10fa, 0x0, 0x0, 0x0, 0x0, 0, 0, 0},
{"__debug_pubnames", "__DWARF", 0x100002141, 0x1b, 0x1141, 0x0, 0x0, 0x0, 0x0, 0, 0, 0},
{"__debug_str", "__DWARF", 0x10000215c, 0x60, 0x115c, 0x0, 0x0, 0x0, 0x0, 0, 0, 0},
},
nil,
},
{
"testdata/clang-386-darwin-exec-with-rpath",
FileHeader{0xfeedface, Cpu386, 0x3, 0x2, 0x10, 0x42c, 0x1200085},
[]any{
nil, // LC_SEGMENT
nil, // LC_SEGMENT
nil, // LC_SEGMENT
nil, // LC_SEGMENT
nil, // LC_DYLD_INFO_ONLY
nil, // LC_SYMTAB
nil, // LC_DYSYMTAB
nil, // LC_LOAD_DYLINKER
nil, // LC_UUID
nil, // LC_VERSION_MIN_MACOSX
nil, // LC_SOURCE_VERSION
nil, // LC_MAIN
nil, // LC_LOAD_DYLIB
&Rpath{LcRpath, "/my/rpath"},
nil, // LC_FUNCTION_STARTS
nil, // LC_DATA_IN_CODE
},
nil,
nil,
},
{
"testdata/clang-amd64-darwin-exec-with-rpath",
FileHeader{0xfeedfacf, CpuAmd64, 0x80000003, 0x2, 0x10, 0x4c8, 0x200085},
[]any{
nil, // LC_SEGMENT
nil, // LC_SEGMENT
nil, // LC_SEGMENT
nil, // LC_SEGMENT
nil, // LC_DYLD_INFO_ONLY
nil, // LC_SYMTAB
nil, // LC_DYSYMTAB
nil, // LC_LOAD_DYLINKER
nil, // LC_UUID
nil, // LC_VERSION_MIN_MACOSX
nil, // LC_SOURCE_VERSION
nil, // LC_MAIN
nil, // LC_LOAD_DYLIB
&Rpath{LcRpath, "/my/rpath"},
nil, // LC_FUNCTION_STARTS
nil, // LC_DATA_IN_CODE
},
nil,
nil,
},
{
"testdata/clang-386-darwin.obj",
FileHeader{0xfeedface, Cpu386, 0x3, 0x1, 0x4, 0x138, 0x2000},
nil,
nil,
map[string][]Reloc{
"__text": {
{
Addr: 0x1d,
Type: uint8(GENERIC_RELOC_VANILLA),
Len: 2,
Pcrel: true,
Extern: true,
Value: 1,
Scattered: false,
},
{
Addr: 0xe,
Type: uint8(GENERIC_RELOC_LOCAL_SECTDIFF),
Len: 2,
Pcrel: false,
Value: 0x2d,
Scattered: true,
},
{
Addr: 0x0,
Type: uint8(GENERIC_RELOC_PAIR),
Len: 2,
Pcrel: false,
Value: 0xb,
Scattered: true,
},
},
},
},
{
"testdata/clang-amd64-darwin.obj",
FileHeader{0xfeedfacf, CpuAmd64, 0x3, 0x1, 0x4, 0x200, 0x2000},
nil,
nil,
map[string][]Reloc{
"__text": {
{
Addr: 0x19,
Type: uint8(X86_64_RELOC_BRANCH),
Len: 2,
Pcrel: true,
Extern: true,
Value: 1,
},
{
Addr: 0xb,
Type: uint8(X86_64_RELOC_SIGNED),
Len: 2,
Pcrel: true,
Extern: false,
Value: 2,
},
},
"__compact_unwind": {
{
Addr: 0x0,
Type: uint8(X86_64_RELOC_UNSIGNED),
Len: 3,
Pcrel: false,
Extern: false,
Value: 1,
},
},
},
},
}
func TestOpen(t *testing.T) {
for i := range fileTests {
tt := &fileTests[i]
f, err := Open(tt.file)
if err != nil {
t.Error(err)
continue
}
if !reflect.DeepEqual(f.FileHeader, tt.hdr) {
t.Errorf("open %s:\n\thave %#v\n\twant %#v\n", tt.file, f.FileHeader, tt.hdr)
continue
}
// for i, l := range f.Loads {
// if len(l.Raw()) < 8 {
// t.Errorf("open %s, command %d:\n\tload command %T don't have enough data\n", tt.file, i, l)
// }
// }
if tt.loads != nil {
for i, l := range f.Loads {
if i >= len(tt.loads) {
break
}
want := tt.loads[i]
if want == nil {
continue
}
switch l := l.(type) {
case *Segment:
have := &l.SegmentHeader
if !reflect.DeepEqual(have, want) {
t.Errorf("open %s, command %d:\n\thave %s\n\twant %s\n", tt.file, i, have.String(), want.(*SegmentHeader).String())
}
case *Dylib:
// have := l
// have.LoadBytes = nil
// if !reflect.DeepEqual(have, want) {
// t.Errorf("open %s, command %d:\n\thave %#v\n\twant %#v\n", tt.file, i, have, want)
// }
case *Rpath:
// have := l
// have.LoadBytes = nil
// if !reflect.DeepEqual(have, want) {
// t.Errorf("open %s, command %d:\n\thave %#v\n\twant %#v\n", tt.file, i, have, want)
// }
default:
t.Errorf("open %s, command %d: unknown load command\n\thave %#v\n\twant %#v\n", tt.file, i, l, want)
}
}
tn := len(tt.loads)
fn := len(f.Loads)
if tn != fn {
t.Errorf("open %s: len(Loads) = %d, want %d", tt.file, fn, tn)
}
}
if tt.sections != nil {
for i, sh := range f.Sections {
if i >= len(tt.sections) {
break
}
have := &sh.SectionHeader
want := tt.sections[i]
if !reflect.DeepEqual(have, want) {
t.Errorf("open %s, section %d:\n\thave %#v\n\twant %#v\n", tt.file, i, have, want)
}
}
tn := len(tt.sections)
fn := len(f.Sections)
if tn != fn {
t.Errorf("open %s: len(Sections) = %d, want %d", tt.file, fn, tn)
}
}
if tt.relocations != nil {
for i, sh := range f.Sections {
have := sh.Relocs
want := tt.relocations[sh.Name]
if !reflect.DeepEqual(have, want) {
t.Errorf("open %s, relocations in section %d (%s):\n\thave %#v\n\twant %#v\n", tt.file, i, sh.Name, have, want)
}
}
}
}
}
func TestOpenFailure(t *testing.T) {
filename := "file.go" // not a Mach-O file
_, err := Open(filename) // don't crash
if err == nil {
t.Errorf("open %s: succeeded unexpectedly", filename)
}
}
func TestOpenFat(t *testing.T) {
ff, err := OpenFat("testdata/fat-gcc-386-amd64-darwin-exec")
if err != nil {
t.Fatal(err)
}
if ff.Magic != MagicFat {
t.Errorf("OpenFat: got magic number %#x, want %#x", ff.Magic, MagicFat)
}
if len(ff.Arches) != 2 {
t.Errorf("OpenFat: got %d architectures, want 2", len(ff.Arches))
}
for i := range ff.Arches {
arch := &ff.Arches[i]
ftArch := &fileTests[i]
if arch.Cpu != ftArch.hdr.Cpu || arch.SubCpu != ftArch.hdr.SubCpu {
t.Errorf("OpenFat: architecture #%d got cpu=%#x subtype=%#x, expected cpu=%#x, subtype=%#x", i, arch.Cpu, arch.SubCpu, ftArch.hdr.Cpu, ftArch.hdr.SubCpu)
}
if !reflect.DeepEqual(arch.FileHeader, ftArch.hdr) {
t.Errorf("OpenFat header:\n\tgot %#v\n\twant %#v\n", arch.FileHeader, ftArch.hdr)
}
}
}
func TestOpenFatFailure(t *testing.T) {
filename := "file.go" // not a Mach-O file
if _, err := OpenFat(filename); err == nil {
t.Errorf("OpenFat %s: succeeded unexpectedly", filename)
}
filename = "testdata/gcc-386-darwin-exec" // not a fat Mach-O
ff, err := OpenFat(filename)
if err == nil {
t.Errorf("OpenFat %s: expected error, got nil", filename)
}
if _, ok := err.(*FormatError); !ok {
t.Errorf("OpenFat %s: expected FormatError, got %v", filename, err)
}
ferr := err.(*FormatError)
if !strings.Contains(ferr.String(), "not a fat") {
t.Errorf("OpenFat %s: expected error containing 'not a fat', got %s", filename, ferr.String())
}
if ff != nil {
t.Errorf("OpenFat %s: got %v, want nil", filename, ff)
}
}
func TestRelocTypeString(t *testing.T) {
if X86_64_RELOC_BRANCH.String() != "X86_64_RELOC_BRANCH" {
t.Errorf("got %v, want %v", X86_64_RELOC_BRANCH.String(), "X86_64_RELOC_BRANCH")
}
if X86_64_RELOC_BRANCH.GoString() != "macho.X86_64_RELOC_BRANCH" {
t.Errorf("got %v, want %v", X86_64_RELOC_BRANCH.GoString(), "macho.X86_64_RELOC_BRANCH")
}
}
func TestTypeString(t *testing.T) {
if MhExecute.String() != "Exec" {
t.Errorf("got %v, want %v", MhExecute.String(), "Exec")
}
if MhExecute.GoString() != "macho.Exec" {
t.Errorf("got %v, want %v", MhExecute.GoString(), "macho.Exec")
}
}
================================================
FILE: cmd/splitdwarf/internal/macho/macho.go
================================================
// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Mach-O header data structures
// http://developer.apple.com/mac/library/documentation/DeveloperTools/Conceptual/MachORuntime/Reference/reference.html
package macho
import (
"encoding/binary"
"strconv"
)
// A FileHeader represents a Mach-O file header.
type FileHeader struct {
Magic uint32
Cpu Cpu
SubCpu uint32
Type HdrType
NCommands uint32 // number of load commands
SizeCommands uint32 // size of all the load commands, not including this header.
Flags HdrFlags
}
func (h *FileHeader) Put(b []byte, o binary.ByteOrder) int {
o.PutUint32(b[0:], h.Magic)
o.PutUint32(b[4:], uint32(h.Cpu))
o.PutUint32(b[8:], h.SubCpu)
o.PutUint32(b[12:], uint32(h.Type))
o.PutUint32(b[16:], h.NCommands)
o.PutUint32(b[20:], h.SizeCommands)
o.PutUint32(b[24:], uint32(h.Flags))
if h.Magic == Magic32 {
return 28
}
o.PutUint32(b[28:], 0)
return 32
}
const (
fileHeaderSize32 = 7 * 4
fileHeaderSize64 = 8 * 4
)
const (
Magic32 uint32 = 0xfeedface
Magic64 uint32 = 0xfeedfacf
MagicFat uint32 = 0xcafebabe
)
type HdrFlags uint32
type SegFlags uint32
type SecFlags uint32
// A HdrType is the Mach-O file type, e.g. an object file, executable, or dynamic library.
type HdrType uint32
const ( // SNAKE_CASE to CamelCase translation from C names
MhObject HdrType = 1
MhExecute HdrType = 2
MhCore HdrType = 4
MhDylib HdrType = 6
MhBundle HdrType = 8
MhDsym HdrType = 0xa
)
var typeStrings = []intName{
{uint32(MhObject), "Obj"},
{uint32(MhExecute), "Exec"},
{uint32(MhDylib), "Dylib"},
{uint32(MhBundle), "Bundle"},
{uint32(MhDsym), "Dsym"},
}
func (t HdrType) String() string { return stringName(uint32(t), typeStrings, false) }
func (t HdrType) GoString() string { return stringName(uint32(t), typeStrings, true) }
// A Cpu is a Mach-O cpu type.
type Cpu uint32
const cpuArch64 = 0x01000000
const (
Cpu386 Cpu = 7
CpuAmd64 Cpu = Cpu386 | cpuArch64
CpuArm Cpu = 12
CpuArm64 Cpu = CpuArm | cpuArch64
CpuPpc Cpu = 18
CpuPpc64 Cpu = CpuPpc | cpuArch64
)
var cpuStrings = []intName{
{uint32(Cpu386), "Cpu386"},
{uint32(CpuAmd64), "CpuAmd64"},
{uint32(CpuArm), "CpuArm"},
{uint32(CpuArm64), "CpuArm64"},
{uint32(CpuPpc), "CpuPpc"},
{uint32(CpuPpc64), "CpuPpc64"},
}
func (i Cpu) String() string { return stringName(uint32(i), cpuStrings, false) }
func (i Cpu) GoString() string { return stringName(uint32(i), cpuStrings, true) }
// A LoadCmd is a Mach-O load command.
type LoadCmd uint32
func (c LoadCmd) Command() LoadCmd { return c }
const ( // SNAKE_CASE to CamelCase translation from C names
// Note 3 and 8 are obsolete
LcSegment LoadCmd = 0x1
LcSymtab LoadCmd = 0x2
LcThread LoadCmd = 0x4
LcUnixthread LoadCmd = 0x5 // thread+stack
LcDysymtab LoadCmd = 0xb
LcDylib LoadCmd = 0xc // load dylib command
LcIdDylib LoadCmd = 0xd // dynamically linked shared lib ident
LcLoadDylinker LoadCmd = 0xe // load a dynamic linker
LcIdDylinker LoadCmd = 0xf // id dylinker command (not load dylinker command)
LcSegment64 LoadCmd = 0x19
LcUuid LoadCmd = 0x1b
LcCodeSignature LoadCmd = 0x1d
LcSegmentSplitInfo LoadCmd = 0x1e
LcRpath LoadCmd = 0x8000001c
LcEncryptionInfo LoadCmd = 0x21
LcDyldInfo LoadCmd = 0x22
LcDyldInfoOnly LoadCmd = 0x80000022
LcVersionMinMacosx LoadCmd = 0x24
LcVersionMinIphoneos LoadCmd = 0x25
LcFunctionStarts LoadCmd = 0x26
LcDyldEnvironment LoadCmd = 0x27
LcMain LoadCmd = 0x80000028 // replacement for UnixThread
LcDataInCode LoadCmd = 0x29 // There are non-instructions in text
LcSourceVersion LoadCmd = 0x2a // Source version used to build binary
LcDylibCodeSignDrs LoadCmd = 0x2b
LcEncryptionInfo64 LoadCmd = 0x2c
LcVersionMinTvos LoadCmd = 0x2f
LcVersionMinWatchos LoadCmd = 0x30
)
var cmdStrings = []intName{
{uint32(LcSegment), "LoadCmdSegment"},
{uint32(LcThread), "LoadCmdThread"},
{uint32(LcUnixthread), "LoadCmdUnixThread"},
{uint32(LcDylib), "LoadCmdDylib"},
{uint32(LcIdDylib), "LoadCmdIdDylib"},
{uint32(LcLoadDylinker), "LoadCmdLoadDylinker"},
{uint32(LcIdDylinker), "LoadCmdIdDylinker"},
{uint32(LcSegment64), "LoadCmdSegment64"},
{uint32(LcUuid), "LoadCmdUuid"},
{uint32(LcRpath), "LoadCmdRpath"},
{uint32(LcDyldEnvironment), "LoadCmdDyldEnv"},
{uint32(LcMain), "LoadCmdMain"},
{uint32(LcDataInCode), "LoadCmdDataInCode"},
{uint32(LcSourceVersion), "LoadCmdSourceVersion"},
{uint32(LcDyldInfo), "LoadCmdDyldInfo"},
{uint32(LcDyldInfoOnly), "LoadCmdDyldInfoOnly"},
{uint32(LcVersionMinMacosx), "LoadCmdMinOsx"},
{uint32(LcFunctionStarts), "LoadCmdFunctionStarts"},
}
func (i LoadCmd) String() string { return stringName(uint32(i), cmdStrings, false) }
func (i LoadCmd) GoString() string { return stringName(uint32(i), cmdStrings, true) }
type (
// A Segment32 is a 32-bit Mach-O segment load command.
Segment32 struct {
LoadCmd
Len uint32
Name [16]byte
Addr uint32
Memsz uint32
Offset uint32
Filesz uint32
Maxprot uint32
Prot uint32
Nsect uint32
Flag SegFlags
}
// A Segment64 is a 64-bit Mach-O segment load command.
Segment64 struct {
LoadCmd
Len uint32
Name [16]byte
Addr uint64
Memsz uint64
Offset uint64
Filesz uint64
Maxprot uint32
Prot uint32
Nsect uint32
Flag SegFlags
}
// A SymtabCmd is a Mach-O symbol table command.
SymtabCmd struct {
LoadCmd
Len uint32
Symoff uint32
Nsyms uint32
Stroff uint32
Strsize uint32
}
// A DysymtabCmd is a Mach-O dynamic symbol table command.
DysymtabCmd struct {
LoadCmd
Len uint32
Ilocalsym uint32
Nlocalsym uint32
Iextdefsym uint32
Nextdefsym uint32
Iundefsym uint32
Nundefsym uint32
Tocoffset uint32
Ntoc uint32
Modtaboff uint32
Nmodtab uint32
Extrefsymoff uint32
Nextrefsyms uint32
Indirectsymoff uint32
Nindirectsyms uint32
Extreloff uint32
Nextrel uint32
Locreloff uint32
Nlocrel uint32
}
// A DylibCmd is a Mach-O load dynamic library command.
DylibCmd struct {
LoadCmd
Len uint32
Name uint32
Time uint32
CurrentVersion uint32
CompatVersion uint32
}
// A DylinkerCmd is a Mach-O load dynamic linker or environment command.
DylinkerCmd struct {
LoadCmd
Len uint32
Name uint32
}
// A RpathCmd is a Mach-O rpath command.
RpathCmd struct {
LoadCmd
Len uint32
Path uint32
}
// A Thread is a Mach-O thread state command.
Thread struct {
LoadCmd
Len uint32
Type uint32
Data []uint32
}
// LC_DYLD_INFO, LC_DYLD_INFO_ONLY
DyldInfoCmd struct {
LoadCmd
Len uint32
RebaseOff, RebaseLen uint32 // file offset and length; data contains segment indices
BindOff, BindLen uint32 // file offset and length; data contains segment indices
WeakBindOff, WeakBindLen uint32 // file offset and length
LazyBindOff, LazyBindLen uint32 // file offset and length
ExportOff, ExportLen uint32 // file offset and length
}
// LC_CODE_SIGNATURE, LC_SEGMENT_SPLIT_INFO, LC_FUNCTION_STARTS, LC_DATA_IN_CODE, LC_DYLIB_CODE_SIGN_DRS
LinkEditDataCmd struct {
LoadCmd
Len uint32
DataOff, DataLen uint32 // file offset and length
}
// LC_ENCRYPTION_INFO, LC_ENCRYPTION_INFO_64
EncryptionInfoCmd struct {
LoadCmd
Len uint32
CryptOff, CryptLen uint32 // file offset and length
CryptId uint32
}
UuidCmd struct {
LoadCmd
Len uint32
Id [16]byte
}
// TODO Commands below not fully supported yet.
EntryPointCmd struct {
LoadCmd
Len uint32
EntryOff uint64 // file offset
StackSize uint64 // if not zero, initial stack size
}
NoteCmd struct {
LoadCmd
Len uint32
Name [16]byte
Offset, Filesz uint64 // file offset and length
}
)
const (
FlagNoUndefs HdrFlags = 0x1
FlagIncrLink HdrFlags = 0x2
FlagDyldLink HdrFlags = 0x4
FlagBindAtLoad HdrFlags = 0x8
FlagPrebound HdrFlags = 0x10
FlagSplitSegs HdrFlags = 0x20
FlagLazyInit HdrFlags = 0x40
FlagTwoLevel HdrFlags = 0x80
FlagForceFlat HdrFlags = 0x100
FlagNoMultiDefs HdrFlags = 0x200
FlagNoFixPrebinding HdrFlags = 0x400
FlagPrebindable HdrFlags = 0x800
FlagAllModsBound HdrFlags = 0x1000
FlagSubsectionsViaSymbols HdrFlags = 0x2000
FlagCanonical HdrFlags = 0x4000
FlagWeakDefines HdrFlags = 0x8000
FlagBindsToWeak HdrFlags = 0x10000
FlagAllowStackExecution HdrFlags = 0x20000
FlagRootSafe HdrFlags = 0x40000
FlagSetuidSafe HdrFlags = 0x80000
FlagNoReexportedDylibs HdrFlags = 0x100000
FlagPIE HdrFlags = 0x200000
FlagDeadStrippableDylib HdrFlags = 0x400000
FlagHasTLVDescriptors HdrFlags = 0x800000
FlagNoHeapExecution HdrFlags = 0x1000000
FlagAppExtensionSafe HdrFlags = 0x2000000
)
// A Section32 is a 32-bit Mach-O section header.
type Section32 struct {
Name [16]byte
Seg [16]byte
Addr uint32
Size uint32
Offset uint32
Align uint32
Reloff uint32
Nreloc uint32
Flags SecFlags
Reserve1 uint32
Reserve2 uint32
}
// A Section64 is a 64-bit Mach-O section header.
type Section64 struct {
Name [16]byte
Seg [16]byte
Addr uint64
Size uint64
Offset uint32
Align uint32
Reloff uint32
Nreloc uint32
Flags SecFlags
Reserve1 uint32
Reserve2 uint32
Reserve3 uint32
}
// An Nlist32 is a Mach-O 32-bit symbol table entry.
type Nlist32 struct {
Name uint32
Type uint8
Sect uint8
Desc uint16
Value uint32
}
// An Nlist64 is a Mach-O 64-bit symbol table entry.
type Nlist64 struct {
Name uint32
Type uint8
Sect uint8
Desc uint16
Value uint64
}
func (n *Nlist64) Put64(b []byte, o binary.ByteOrder) uint32 {
o.PutUint32(b[0:], n.Name)
b[4] = byte(n.Type)
b[5] = byte(n.Sect)
o.PutUint16(b[6:], n.Desc)
o.PutUint64(b[8:], n.Value)
return 8 + 8
}
func (n *Nlist64) Put32(b []byte, o binary.ByteOrder) uint32 {
o.PutUint32(b[0:], n.Name)
b[4] = byte(n.Type)
b[5] = byte(n.Sect)
o.PutUint16(b[6:], n.Desc)
o.PutUint32(b[8:], uint32(n.Value))
return 8 + 4
}
// Regs386 is the Mach-O 386 register structure.
type Regs386 struct {
AX uint32
BX uint32
CX uint32
DX uint32
DI uint32
SI uint32
BP uint32
SP uint32
SS uint32
FLAGS uint32
IP uint32
CS uint32
DS uint32
ES uint32
FS uint32
GS uint32
}
// RegsAMD64 is the Mach-O AMD64 register structure.
type RegsAMD64 struct {
AX uint64
BX uint64
CX uint64
DX uint64
DI uint64
SI uint64
BP uint64
SP uint64
R8 uint64
R9 uint64
R10 uint64
R11 uint64
R12 uint64
R13 uint64
R14 uint64
R15 uint64
IP uint64
FLAGS uint64
CS uint64
FS uint64
GS uint64
}
type intName struct {
i uint32
s string
}
func stringName(i uint32, names []intName, goSyntax bool) string {
for _, n := range names {
if n.i == i {
if goSyntax {
return "macho." + n.s
}
return n.s
}
}
return "0x" + strconv.FormatUint(uint64(i), 16)
}
================================================
FILE: cmd/splitdwarf/internal/macho/reloctype.go
================================================
// Copyright 2017 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package macho
//go:generate stringer -type=RelocTypeGeneric,RelocTypeX86_64,RelocTypeARM,RelocTypeARM64 -output reloctype_string.go
type RelocTypeGeneric int
const (
GENERIC_RELOC_VANILLA RelocTypeGeneric = 0
GENERIC_RELOC_PAIR RelocTypeGeneric = 1
GENERIC_RELOC_SECTDIFF RelocTypeGeneric = 2
GENERIC_RELOC_PB_LA_PTR RelocTypeGeneric = 3
GENERIC_RELOC_LOCAL_SECTDIFF RelocTypeGeneric = 4
GENERIC_RELOC_TLV RelocTypeGeneric = 5
)
func (r RelocTypeGeneric) GoString() string { return "macho." + r.String() }
type RelocTypeX86_64 int
const (
X86_64_RELOC_UNSIGNED RelocTypeX86_64 = 0
X86_64_RELOC_SIGNED RelocTypeX86_64 = 1
X86_64_RELOC_BRANCH RelocTypeX86_64 = 2
X86_64_RELOC_GOT_LOAD RelocTypeX86_64 = 3
X86_64_RELOC_GOT RelocTypeX86_64 = 4
X86_64_RELOC_SUBTRACTOR RelocTypeX86_64 = 5
X86_64_RELOC_SIGNED_1 RelocTypeX86_64 = 6
X86_64_RELOC_SIGNED_2 RelocTypeX86_64 = 7
X86_64_RELOC_SIGNED_4 RelocTypeX86_64 = 8
X86_64_RELOC_TLV RelocTypeX86_64 = 9
)
func (r RelocTypeX86_64) GoString() string { return "macho." + r.String() }
type RelocTypeARM int
const (
ARM_RELOC_VANILLA RelocTypeARM = 0
ARM_RELOC_PAIR RelocTypeARM = 1
ARM_RELOC_SECTDIFF RelocTypeARM = 2
ARM_RELOC_LOCAL_SECTDIFF RelocTypeARM = 3
ARM_RELOC_PB_LA_PTR RelocTypeARM = 4
ARM_RELOC_BR24 RelocTypeARM = 5
ARM_THUMB_RELOC_BR22 RelocTypeARM = 6
ARM_THUMB_32BIT_BRANCH RelocTypeARM = 7
ARM_RELOC_HALF RelocTypeARM = 8
ARM_RELOC_HALF_SECTDIFF RelocTypeARM = 9
)
func (r RelocTypeARM) GoString() string { return "macho." + r.String() }
type RelocTypeARM64 int
const (
ARM64_RELOC_UNSIGNED RelocTypeARM64 = 0
ARM64_RELOC_SUBTRACTOR RelocTypeARM64 = 1
ARM64_RELOC_BRANCH26 RelocTypeARM64 = 2
ARM64_RELOC_PAGE21 RelocTypeARM64 = 3
ARM64_RELOC_PAGEOFF12 RelocTypeARM64 = 4
ARM64_RELOC_GOT_LOAD_PAGE21 RelocTypeARM64 = 5
ARM64_RELOC_GOT_LOAD_PAGEOFF12 RelocTypeARM64 = 6
ARM64_RELOC_POINTER_TO_GOT RelocTypeARM64 = 7
ARM64_RELOC_TLVP_LOAD_PAGE21 RelocTypeARM64 = 8
ARM64_RELOC_TLVP_LOAD_PAGEOFF12 RelocTypeARM64 = 9
ARM64_RELOC_ADDEND RelocTypeARM64 = 10
)
func (r RelocTypeARM64) GoString() string { return "macho." + r.String() }
================================================
FILE: cmd/splitdwarf/internal/macho/reloctype_string.go
================================================
// Code generated by "stringer -type=RelocTypeGeneric,RelocTypeX86_64,RelocTypeARM,RelocTypeARM64 -output reloctype_string.go"; DO NOT EDIT.
package macho
import "strconv"
func _() {
// An "invalid array index" compiler error signifies that the constant values have changed.
// Re-run the stringer command to generate them again.
var x [1]struct{}
_ = x[GENERIC_RELOC_VANILLA-0]
_ = x[GENERIC_RELOC_PAIR-1]
_ = x[GENERIC_RELOC_SECTDIFF-2]
_ = x[GENERIC_RELOC_PB_LA_PTR-3]
_ = x[GENERIC_RELOC_LOCAL_SECTDIFF-4]
_ = x[GENERIC_RELOC_TLV-5]
}
const _RelocTypeGeneric_name = "GENERIC_RELOC_VANILLAGENERIC_RELOC_PAIRGENERIC_RELOC_SECTDIFFGENERIC_RELOC_PB_LA_PTRGENERIC_RELOC_LOCAL_SECTDIFFGENERIC_RELOC_TLV"
var _RelocTypeGeneric_index = [...]uint8{0, 21, 39, 61, 84, 112, 129}
func (i RelocTypeGeneric) String() string {
if i < 0 || i >= RelocTypeGeneric(len(_RelocTypeGeneric_index)-1) {
return "RelocTypeGeneric(" + strconv.FormatInt(int64(i), 10) + ")"
}
return _RelocTypeGeneric_name[_RelocTypeGeneric_index[i]:_RelocTypeGeneric_index[i+1]]
}
func _() {
// An "invalid array index" compiler error signifies that the constant values have changed.
// Re-run the stringer command to generate them again.
var x [1]struct{}
_ = x[X86_64_RELOC_UNSIGNED-0]
_ = x[X86_64_RELOC_SIGNED-1]
_ = x[X86_64_RELOC_BRANCH-2]
_ = x[X86_64_RELOC_GOT_LOAD-3]
_ = x[X86_64_RELOC_GOT-4]
_ = x[X86_64_RELOC_SUBTRACTOR-5]
_ = x[X86_64_RELOC_SIGNED_1-6]
_ = x[X86_64_RELOC_SIGNED_2-7]
_ = x[X86_64_RELOC_SIGNED_4-8]
_ = x[X86_64_RELOC_TLV-9]
}
const _RelocTypeX86_64_name = "X86_64_RELOC_UNSIGNEDX86_64_RELOC_SIGNEDX86_64_RELOC_BRANCHX86_64_RELOC_GOT_LOADX86_64_RELOC_GOTX86_64_RELOC_SUBTRACTORX86_64_RELOC_SIGNED_1X86_64_RELOC_SIGNED_2X86_64_RELOC_SIGNED_4X86_64_RELOC_TLV"
var _RelocTypeX86_64_index = [...]uint8{0, 21, 40, 59, 80, 96, 119, 140, 161, 182, 198}
func (i RelocTypeX86_64) String() string {
if i < 0 || i >= RelocTypeX86_64(len(_RelocTypeX86_64_index)-1) {
return "RelocTypeX86_64(" + strconv.FormatInt(int64(i), 10) + ")"
}
return _RelocTypeX86_64_name[_RelocTypeX86_64_index[i]:_RelocTypeX86_64_index[i+1]]
}
func _() {
// An "invalid array index" compiler error signifies that the constant values have changed.
// Re-run the stringer command to generate them again.
var x [1]struct{}
_ = x[ARM_RELOC_VANILLA-0]
_ = x[ARM_RELOC_PAIR-1]
_ = x[ARM_RELOC_SECTDIFF-2]
_ = x[ARM_RELOC_LOCAL_SECTDIFF-3]
_ = x[ARM_RELOC_PB_LA_PTR-4]
_ = x[ARM_RELOC_BR24-5]
_ = x[ARM_THUMB_RELOC_BR22-6]
_ = x[ARM_THUMB_32BIT_BRANCH-7]
_ = x[ARM_RELOC_HALF-8]
_ = x[ARM_RELOC_HALF_SECTDIFF-9]
}
const _RelocTypeARM_name = "ARM_RELOC_VANILLAARM_RELOC_PAIRARM_RELOC_SECTDIFFARM_RELOC_LOCAL_SECTDIFFARM_RELOC_PB_LA_PTRARM_RELOC_BR24ARM_THUMB_RELOC_BR22ARM_THUMB_32BIT_BRANCHARM_RELOC_HALFARM_RELOC_HALF_SECTDIFF"
var _RelocTypeARM_index = [...]uint8{0, 17, 31, 49, 73, 92, 106, 126, 148, 162, 185}
func (i RelocTypeARM) String() string {
if i < 0 || i >= RelocTypeARM(len(_RelocTypeARM_index)-1) {
return "RelocTypeARM(" + strconv.FormatInt(int64(i), 10) + ")"
}
return _RelocTypeARM_name[_RelocTypeARM_index[i]:_RelocTypeARM_index[i+1]]
}
func _() {
// An "invalid array index" compiler error signifies that the constant values have changed.
// Re-run the stringer command to generate them again.
var x [1]struct{}
_ = x[ARM64_RELOC_UNSIGNED-0]
_ = x[ARM64_RELOC_SUBTRACTOR-1]
_ = x[ARM64_RELOC_BRANCH26-2]
_ = x[ARM64_RELOC_PAGE21-3]
_ = x[ARM64_RELOC_PAGEOFF12-4]
_ = x[ARM64_RELOC_GOT_LOAD_PAGE21-5]
_ = x[ARM64_RELOC_GOT_LOAD_PAGEOFF12-6]
_ = x[ARM64_RELOC_POINTER_TO_GOT-7]
_ = x[ARM64_RELOC_TLVP_LOAD_PAGE21-8]
_ = x[ARM64_RELOC_TLVP_LOAD_PAGEOFF12-9]
_ = x[ARM64_RELOC_ADDEND-10]
}
const _RelocTypeARM64_name = "ARM64_RELOC_UNSIGNEDARM64_RELOC_SUBTRACTORARM64_RELOC_BRANCH26ARM64_RELOC_PAGE21ARM64_RELOC_PAGEOFF12ARM64_RELOC_GOT_LOAD_PAGE21ARM64_RELOC_GOT_LOAD_PAGEOFF12ARM64_RELOC_POINTER_TO_GOTARM64_RELOC_TLVP_LOAD_PAGE21ARM64_RELOC_TLVP_LOAD_PAGEOFF12ARM64_RELOC_ADDEND"
var _RelocTypeARM64_index = [...]uint16{0, 20, 42, 62, 80, 101, 128, 158, 184, 212, 243, 261}
func (i RelocTypeARM64) String() string {
if i < 0 || i >= RelocTypeARM64(len(_RelocTypeARM64_index)-1) {
return "RelocTypeARM64(" + strconv.FormatInt(int64(i), 10) + ")"
}
return _RelocTypeARM64_name[_RelocTypeARM64_index[i]:_RelocTypeARM64_index[i+1]]
}
================================================
FILE: cmd/splitdwarf/internal/macho/testdata/hello.c
================================================
#include
int
main(void)
{
printf("hello, world\n");
return 0;
}
================================================
FILE: cmd/splitdwarf/splitdwarf.go
================================================
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build aix || darwin || dragonfly || freebsd || linux || netbsd || openbsd
/*
Splitdwarf uncompresses and copies the DWARF segment of a Mach-O
executable into the "dSYM" file expected by lldb and ports of gdb
on OSX.
Usage: splitdwarf osxMachoFile [ osxDsymFile ]
Unless a dSYM file name is provided on the command line,
splitdwarf will place it where the OSX tools expect it, in
".dSYM/Contents/Resources/DWARF/",
creating directories as necessary.
*/
package main // import "golang.org/x/tools/cmd/splitdwarf"
import (
"crypto/sha256"
"fmt"
"io"
"os"
"path/filepath"
"strings"
"syscall"
"golang.org/x/tools/cmd/splitdwarf/internal/macho"
)
const (
pageAlign = 12 // 4096 = 1 << 12
)
func note(format string, why ...any) {
fmt.Fprintf(os.Stderr, format+"\n", why...)
}
func fail(format string, why ...any) {
note(format, why...)
os.Exit(1)
}
// splitdwarf inputexe [ outputdwarf ]
func main() {
if len(os.Args) < 2 || len(os.Args) > 3 {
fmt.Printf(`
Usage: %s input_exe [ output_dsym ]
Reads the executable input_exe, uncompresses and copies debugging
information into output_dsym. If output_dsym is not specified,
the path
input_exe.dSYM/Contents/Resources/DWARF/input_exe
is used instead. That is the path that gdb and lldb expect
on OSX. Input_exe needs a UUID segment; if that is missing,
then one is created and added. In that case, the permissions
for input_exe need to allow writing.
`, os.Args[0])
return
}
// Read input, find DWARF, be sure it looks right
inputExe := os.Args[1]
exeFile, err := os.Open(inputExe)
if err != nil {
fail("%v", err)
}
exeMacho, err := macho.NewFile(exeFile)
if err != nil {
fail("(internal) Couldn't create macho, %v", err)
}
// Postpone dealing with output till input is known-good
// describe(&exeMacho.FileTOC)
// Offsets into __LINKEDIT:
//
// Command LC_SYMTAB =
// (1) number of symbols at file offset (within link edit section) of 16-byte symbol table entries
// struct {
// StringTableIndex uint32
// Type, SectionIndex uint8
// Description uint16
// Value uint64
// }
//
// (2) string table offset and size. Strings are zero-byte terminated. First must be " ".
//
// Command LC_DYSYMTAB = indices within symtab (above), except for IndSym
// IndSym Offset = file offset (within link edit section) of 4-byte indices within symtab.
//
// Section __TEXT.__symbol_stub1.
// Offset and size (Reserved2) locate and describe a table for this section.
// Symbols beginning at IndirectSymIndex (Reserved1) (see LC_DYSYMTAB.IndSymOffset) refer to this table.
// (These table entries are apparently PLTs [Procedure Linkage Table/Trampoline])
//
// Section __DATA.__nl_symbol_ptr.
// Reserved1 seems to be an index within the Indirect symbols (see LC_DYSYMTAB.IndSymOffset)
// Some of these symbols appear to be duplicates of other indirect symbols appearing early
//
// Section __DATA.__la_symbol_ptr.
// Reserved1 seems to be an index within the Indirect symbols (see LC_DYSYMTAB.IndSymOffset)
// Some of these symbols appear to be duplicates of other indirect symbols appearing early
//
// Create a File for the output dwarf.
// Copy header, file type is MH_DSYM
// Copy the relevant load commands
// LoadCmdUuid
// Symtab -- very abbreviated (Use DYSYMTAB Iextdefsym, Nextdefsym to identify these).
// Segment __PAGEZERO
// Segment __TEXT (zero the size, zero the offset of each section)
// Segment __DATA (zero the size, zero the offset of each section)
// Segment __LINKEDIT (contains the symbols and strings from Symtab)
// Segment __DWARF (uncompressed)
var uuid *macho.Uuid
for _, l := range exeMacho.Loads {
switch l.Command() {
case macho.LcUuid:
uuid = l.(*macho.Uuid)
}
}
// Ensure a given load is not nil
nonnilC := func(l macho.Load, s string) {
if l == nil {
fail("input file %s lacks load command %s", inputExe, s)
}
}
// Find a segment by name and ensure it is not nil
nonnilS := func(s string) *macho.Segment {
l := exeMacho.Segment(s)
if l == nil {
fail("input file %s lacks segment %s", inputExe, s)
}
return l
}
newtoc := exeMacho.FileTOC.DerivedCopy(macho.MhDsym, 0)
symtab := exeMacho.Symtab
dysymtab := exeMacho.Dysymtab // Not appearing in output, but necessary to construct output
nonnilC(symtab, "symtab")
nonnilC(dysymtab, "dysymtab")
text := nonnilS("__TEXT")
data := nonnilS("__DATA")
linkedit := nonnilS("__LINKEDIT")
pagezero := nonnilS("__PAGEZERO")
newtext := text.CopyZeroed()
newdata := data.CopyZeroed()
newsymtab := symtab.Copy()
// Linkedit segment contain symbols and strings;
// Symtab refers to offsets into linkedit.
// This next bit initializes newsymtab and sets up data structures for the linkedit segment
linkeditsyms := []macho.Nlist64{}
linkeditstrings := []string{}
// Linkedit will begin at the second page, i.e., offset is one page from beginning
// Symbols come first
linkeditsymbase := uint32(1) << pageAlign
// Strings come second, offset by the number of symbols times their size.
// Only those symbols from dysymtab.defsym are written into the debugging information.
linkeditstringbase := linkeditsymbase + exeMacho.FileTOC.SymbolSize()*dysymtab.Nextdefsym
// The first two bytes of the strings are reserved for space, null (' ', \000)
linkeditstringcur := uint32(2)
newsymtab.Syms = newsymtab.Syms[:0]
newsymtab.Symoff = linkeditsymbase
newsymtab.Stroff = linkeditstringbase
newsymtab.Nsyms = dysymtab.Nextdefsym
for i := uint32(0); i < dysymtab.Nextdefsym; i++ {
ii := i + dysymtab.Iextdefsym
oldsym := symtab.Syms[ii]
newsymtab.Syms = append(newsymtab.Syms, oldsym)
linkeditsyms = append(linkeditsyms, macho.Nlist64{Name: linkeditstringcur,
Type: oldsym.Type, Sect: oldsym.Sect, Desc: oldsym.Desc, Value: oldsym.Value})
linkeditstringcur += uint32(len(oldsym.Name)) + 1
linkeditstrings = append(linkeditstrings, oldsym.Name)
}
newsymtab.Strsize = linkeditstringcur
exeNeedsUuid := uuid == nil
if exeNeedsUuid {
uuid = &macho.Uuid{UuidCmd: macho.UuidCmd{LoadCmd: macho.LcUuid}}
uuid.Len = uuid.LoadSize(newtoc)
copy(uuid.Id[0:], contentuuid(&exeMacho.FileTOC)[0:16])
uuid.Id[6] = uuid.Id[6]&^0xf0 | 0x40 // version 4 (pseudo-random); see section 4.1.3
uuid.Id[8] = uuid.Id[8]&^0xc0 | 0x80 // variant bits; see section 4.1.1
}
newtoc.AddLoad(uuid)
// For the specified segment (assumed to be in exeMacho) make a copy of its
// sections with appropriate fields zeroed out, and append them to the
// currently-last segment in newtoc.
copyZOdSections := func(g *macho.Segment) {
for i := g.Firstsect; i < g.Firstsect+g.Nsect; i++ {
s := exeMacho.Sections[i].Copy()
s.Offset = 0
s.Reloff = 0
s.Nreloc = 0
newtoc.AddSection(s)
}
}
newtoc.AddLoad(newsymtab)
newtoc.AddSegment(pagezero)
newtoc.AddSegment(newtext)
copyZOdSections(text)
newtoc.AddSegment(newdata)
copyZOdSections(data)
newlinkedit := linkedit.Copy()
newlinkedit.Offset = uint64(linkeditsymbase)
newlinkedit.Filesz = uint64(linkeditstringcur)
newlinkedit.Addr = macho.RoundUp(newdata.Addr+newdata.Memsz, 1< 2 {
outDwarf = os.Args[2]
} else {
err := os.MkdirAll(outDwarf, 0755)
if err != nil {
fail("%v", err)
}
outDwarf = filepath.Join(outDwarf, filepath.Base(inputExe))
}
dwarfFile, buffer := CreateMmapFile(outDwarf, int64(newtoc.FileSize()))
// (1) Linkedit segment
// Symbol table
offset = uint32(newlinkedit.Offset)
for i := range linkeditsyms {
if exeMacho.Magic == macho.Magic64 {
offset += linkeditsyms[i].Put64(buffer[offset:], newtoc.ByteOrder)
} else {
offset += linkeditsyms[i].Put32(buffer[offset:], newtoc.ByteOrder)
}
}
// Initial two bytes of string table, followed by actual zero-terminated strings.
buffer[linkeditstringbase] = ' '
buffer[linkeditstringbase+1] = 0
offset = linkeditstringbase + 2
for _, str := range linkeditstrings {
for i := 0; i < len(str); i++ {
buffer[offset] = str[i]
offset++
}
buffer[offset] = 0
offset++
}
// (2) DWARF segment
ioff := newdwarf.Firstsect - dwarf.Firstsect
for i := dwarf.Firstsect; i < dwarf.Firstsect+dwarf.Nsect; i++ {
s := exeMacho.Sections[i]
j := i + ioff
s.PutUncompressedData(buffer[newtoc.Sections[j].Offset:])
}
// Because "text" overlaps the header and the loads, write them afterwards, just in case.
// Write header.
newtoc.Put(buffer)
err = syscall.Munmap(buffer)
if err != nil {
fail("Munmap %s for dwarf output failed, %v", outDwarf, err)
}
err = dwarfFile.Close()
if err != nil {
fail("Close %s for dwarf output after mmap/munmap failed, %v", outDwarf, err)
}
if exeNeedsUuid { // Map the original exe, modify the header, and write the UUID command
hdr := exeMacho.FileTOC.FileHeader
oldCommandEnd := hdr.SizeCommands + newtoc.HdrSize()
hdr.NCommands += 1
hdr.SizeCommands += uuid.LoadSize(newtoc)
mapf, err := os.OpenFile(inputExe, os.O_RDWR, 0)
if err != nil {
fail("Updating UUID in binary failed, %v", err)
}
exebuf, err := syscall.Mmap(int(mapf.Fd()), 0, int(macho.RoundUp(uint64(hdr.SizeCommands), 1< 0 {
return fmt.Errorf("packages contain errors")
}
if !*runFlag {
// Create (and display) SSA only for initial packages and wrappers.
_, pkgs := ssautil.Packages(initial, mode)
for i, p := range pkgs {
if p == nil {
return fmt.Errorf("cannot build SSA for package %s", initial[i])
}
p.Build()
}
} else {
// Create SSA for initial packages and all dependencies, instantiating generics.
mode |= ssa.InstantiateGenerics
// TODO(adonovan): opt: use noreturn analysis over transitive
// dependencies to prune spurious control flow graph edges.
prog, pkgs := ssautil.AllPackages(initial, mode)
for i, p := range pkgs {
if p == nil {
return fmt.Errorf("cannot build SSA for package %s", initial[i])
}
}
// Run the interpreter.
// Build SSA for all packages.
prog.Build()
// Earlier versions of the interpreter needed the runtime
// package; however, interp cannot handle unsafe constructs
// used during runtime's package initialization at the moment.
// The key construct blocking support is:
// *((*T)(unsafe.Pointer(p)))
// Unfortunately, this means only trivial programs can be
// interpreted by ssadump.
if prog.ImportedPackage("runtime") != nil {
return fmt.Errorf("-run: program depends on runtime package (interpreter can run only trivial programs)")
}
if runtime.GOARCH != build.Default.GOARCH {
return fmt.Errorf("cross-interpretation is not supported (target has GOARCH %s, interpreter has %s)",
build.Default.GOARCH, runtime.GOARCH)
}
// Run first main package.
for _, main := range ssautil.MainPackages(pkgs) {
fmt.Fprintf(os.Stderr, "Running: %s\n", main.Pkg.Path())
os.Exit(interp.Interpret(main, interpMode, sizes, main.Pkg.Path(), args))
}
return fmt.Errorf("no main package")
}
return nil
}
// stringListValue is a flag.Value that accumulates strings.
// e.g. --flag=one --flag=two would produce []string{"one", "two"}.
type stringListValue []string
func (ss *stringListValue) Get() any { return []string(*ss) }
func (ss *stringListValue) String() string { return fmt.Sprintf("%q", *ss) }
func (ss *stringListValue) Set(s string) error { *ss = append(*ss, s); return nil }
================================================
FILE: cmd/stress/stress.go
================================================
// Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build unix || aix || darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris || windows
// The stress utility is intended for catching sporadic failures.
// It runs a given process in parallel in a loop and collects any failures.
// Usage:
//
// $ stress ./fmt.test -test.run=TestSometing -test.cpu=10
//
// You can also specify a number of parallel processes with -p flag;
// instruct the utility to not kill hanged processes for gdb attach;
// or specify the failure output you are looking for (if you want to
// ignore some other sporadic failures).
package main
import (
"bytes"
"flag"
"fmt"
"os"
"os/exec"
"path/filepath"
"regexp"
"runtime"
"sync/atomic"
"syscall"
"time"
)
var (
flagCount = flag.Int("count", 0, "stop after `N` runs (default never stop)")
flagFailfast = flag.Bool("failfast", false, "exit on first failure and write failure output to stderr")
flagFailure = flag.String("failure", "", "fail only if output matches `regexp`")
flagIgnore = flag.String("ignore", "", "ignore failure if output matches `regexp`")
flagKill = flag.Bool("kill", true, "kill timed out processes if true, otherwise just print pid (to attach with gdb)")
flagOutput = flag.String("o", defaultPrefix(), "output failure logs to `path` plus a unique suffix")
flagP = flag.Int("p", runtime.NumCPU(), "run `N` processes in parallel")
flagTimeout = flag.Duration("timeout", 10*time.Minute, "timeout each process after `duration`")
)
func init() {
flag.Usage = func() {
os.Stderr.WriteString(`The stress utility is intended for catching sporadic failures.
It runs a given process in parallel in a loop and collects any failures.
Usage:
$ stress ./fmt.test -test.run=TestSometing -test.cpu=10
`)
flag.PrintDefaults()
}
}
func defaultPrefix() string {
date := time.Now().Format("go-stress-20060102T150405-")
return filepath.Join(os.TempDir(), date)
}
func main() {
flag.Parse()
if *flagP <= 0 || *flagTimeout <= 0 || len(flag.Args()) == 0 {
flag.Usage()
os.Exit(1)
}
var failureRe, ignoreRe *regexp.Regexp
if *flagFailure != "" {
var err error
if failureRe, err = regexp.Compile(*flagFailure); err != nil {
fmt.Println("bad failure regexp:", err)
os.Exit(1)
}
}
if *flagIgnore != "" {
var err error
if ignoreRe, err = regexp.Compile(*flagIgnore); err != nil {
fmt.Println("bad ignore regexp:", err)
os.Exit(1)
}
}
res := make(chan []byte)
var started atomic.Int64
for i := 0; i < *flagP; i++ {
go func() {
for {
// Note: Must started.Add(1) even if not using -count,
// because it enables the '%d active' print below.
if started.Add(1) > int64(*flagCount) && *flagCount > 0 {
break
}
cmd := exec.Command(flag.Args()[0], flag.Args()[1:]...)
var buf bytes.Buffer
cmd.Stdout = &buf
cmd.Stderr = &buf
err := cmd.Start() // make cmd.Process valid for timeout goroutine
done := make(chan bool)
if err == nil && *flagTimeout > 0 {
go func() {
select {
case <-done:
return
case <-time.After(*flagTimeout):
}
if !*flagKill {
fmt.Printf("process %v timed out\n", cmd.Process.Pid)
return
}
cmd.Process.Signal(syscall.SIGABRT)
select {
case <-done:
return
case <-time.After(10 * time.Second):
}
cmd.Process.Kill()
}()
}
if err == nil {
err = cmd.Wait()
}
out := buf.Bytes()
close(done)
if err != nil && (failureRe == nil || failureRe.Match(out)) && (ignoreRe == nil || !ignoreRe.Match(out)) {
out = append(out, fmt.Sprintf("\n\nERROR: %v\n", err)...)
} else {
out = []byte{}
}
res <- out
}
}()
}
runs, fails := 0, 0
start := time.Now()
ticker := time.NewTicker(5 * time.Second).C
status := func(context string) {
elapsed := time.Since(start).Truncate(time.Second)
var pct string
if fails > 0 {
pct = fmt.Sprintf(" (%0.2f%%)", 100.0*float64(fails)/float64(runs))
}
var active string
n := started.Load() - int64(runs)
if *flagCount > 0 {
// started counts past *flagCount at end; do not count those
// TODO: n = min(n, int64(*flagCount-runs))
if x := int64(*flagCount - runs); n > x {
n = x
}
}
if n > 0 {
active = fmt.Sprintf(", %d active", n)
}
fmt.Printf("%v: %v runs %s, %v failures%s%s\n", elapsed, runs, context, fails, pct, active)
}
for {
select {
case out := <-res:
runs++
if len(out) > 0 {
fails++
if *flagFailfast {
os.Stderr.Write(out)
os.Exit(1)
}
dir, path := filepath.Split(*flagOutput)
f, err := os.CreateTemp(dir, path)
if err != nil {
fmt.Printf("failed to create temp file: %v\n", err)
os.Exit(1)
}
f.Write(out)
f.Close()
if len(out) > 2<<10 {
out := out[:2<<10]
fmt.Printf("\n%s\n%s\n…\n", f.Name(), out)
} else {
fmt.Printf("\n%s\n%s\n", f.Name(), out)
}
}
if *flagCount > 0 && runs >= *flagCount {
status("total")
if fails > 0 {
os.Exit(1)
}
os.Exit(0)
}
case <-ticker:
status("so far")
}
}
}
================================================
FILE: cmd/stringer/endtoend_test.go
================================================
// Copyright 2014 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// go command is not available on android
//go:build !android
package main
import (
"bytes"
"flag"
"fmt"
"io"
"os"
"path"
"path/filepath"
"reflect"
"slices"
"strings"
"sync"
"testing"
"golang.org/x/tools/internal/testenv"
)
// This file contains a test that compiles and runs each program in testdata
// after generating the string method for its type. The rule is that for testdata/x.go
// we run stringer -type X and then compile and run the program. The resulting
// binary panics if the String method for X is not correct, including for error cases.
func TestMain(m *testing.M) {
if os.Getenv("STRINGER_TEST_IS_STRINGER") != "" {
main()
os.Exit(0)
}
// Inform subprocesses that they should run the cmd/stringer main instead of
// running tests. It's a close approximation to building and running the real
// command, and much less complicated and expensive to build and clean up.
os.Setenv("STRINGER_TEST_IS_STRINGER", "1")
flag.Parse()
if testing.Verbose() {
os.Setenv("GOPACKAGESDEBUG", "true")
}
os.Exit(m.Run())
}
func TestEndToEnd(t *testing.T) {
testenv.NeedsTool(t, "go")
stringer := stringerPath(t)
// Read the testdata directory.
fd, err := os.Open("testdata")
if err != nil {
t.Fatal(err)
}
defer fd.Close()
names, err := fd.Readdirnames(-1)
if err != nil {
t.Fatalf("Readdirnames: %s", err)
}
// Generate, compile, and run the test programs.
for _, name := range names {
if name == "typeparams" {
// ignore the directory containing the tests with type params
continue
}
if !strings.HasSuffix(name, ".go") {
t.Errorf("%s is not a Go file", name)
continue
}
if strings.HasPrefix(name, "tag_") || strings.HasPrefix(name, "vary_") {
// This file is used for tag processing in TestTags or TestConstValueChange, below.
continue
}
t.Run(name, func(t *testing.T) {
if name == "cgo.go" {
testenv.NeedsTool(t, "cgo")
}
stringerCompileAndRun(t, t.TempDir(), stringer, typeName(name), name)
})
}
}
// a type name for stringer. use the last component of the file name with the .go
func typeName(fname string) string {
// file names are known to be ascii and end .go
base := path.Base(fname)
return fmt.Sprintf("%c%s", base[0]+'A'-'a', base[1:len(base)-len(".go")])
}
// TestTags verifies that the -tags flag works as advertised.
func TestTags(t *testing.T) {
stringer := stringerPath(t)
dir := t.TempDir()
var (
protectedConst = []byte("TagProtected")
output = filepath.Join(dir, "const_string.go")
)
for _, file := range []string{"tag_main.go", "tag_tag.go"} {
err := copy(filepath.Join(dir, file), filepath.Join("testdata", file))
if err != nil {
t.Fatal(err)
}
}
// Run stringer in the directory that contains the module that contains the package files.
if err := os.WriteFile(filepath.Join(dir, "go.mod"), []byte("module test\n"), 0o600); err != nil {
t.Fatal(err)
}
err := runInDir(t, dir, stringer, "-type", "Const", ".")
if err != nil {
t.Fatal(err)
}
result, err := os.ReadFile(output)
if err != nil {
t.Fatal(err)
}
if bytes.Contains(result, protectedConst) {
t.Fatal("tagged variable appears in untagged run")
}
err = os.Remove(output)
if err != nil {
t.Fatal(err)
}
err = runInDir(t, dir, stringer, "-type", "Const", "-tags", "tag", ".")
if err != nil {
t.Fatal(err)
}
result, err = os.ReadFile(output)
if err != nil {
t.Fatal(err)
}
if !bytes.Contains(result, protectedConst) {
t.Fatal("tagged variable does not appear in tagged run")
}
}
// TestConstValueChange verifies that if a constant value changes and
// the stringer code is not regenerated, we'll get a compiler error.
func TestConstValueChange(t *testing.T) {
testenv.NeedsTool(t, "go")
stringer := stringerPath(t)
dir := t.TempDir()
source := filepath.Join(dir, "day.go")
err := copy(source, filepath.Join("testdata", "day.go"))
if err != nil {
t.Fatal(err)
}
stringSource := filepath.Join(dir, "day_string.go")
// Run stringer in the directory that contains the module that contains the package files.
if err := os.WriteFile(filepath.Join(dir, "go.mod"), []byte("module test\n"), 0o600); err != nil {
t.Fatal(err)
}
err = runInDir(t, dir, stringer, "-type", "Day", "-output", stringSource)
if err != nil {
t.Fatal(err)
}
// Run the binary in the temporary directory as a sanity check.
err = run(t, "go", "run", stringSource, source)
if err != nil {
t.Fatal(err)
}
// Overwrite the source file with a version that has changed constants.
err = copy(source, filepath.Join("testdata", "vary_day.go"))
if err != nil {
t.Fatal(err)
}
// Unfortunately different compilers may give different error messages,
// so there's no easy way to verify that the build failed specifically
// because the constants changed rather than because the vary_day.go
// file is invalid.
//
// Instead we'll just rely on manual inspection of the polluted test
// output. An alternative might be to check that the error output
// matches a set of possible error strings emitted by known
// Go compilers.
t.Logf("Note: the following messages should indicate an out-of-bounds compiler error\n")
err = run(t, "go", "build", stringSource, source)
if err == nil {
t.Fatal("unexpected compiler success")
}
}
var testfileSrcs = map[string]string{
"go.mod": "module foo",
// Normal file in the package.
"main.go": `package foo
type Foo int
const (
fooX Foo = iota
fooY
fooZ
)
`,
// Test file in the package.
"main_test.go": `package foo
type Bar int
const (
barX Bar = iota
barY
barZ
)
`,
// Test file in the test package.
"main_pkg_test.go": `package foo_test
type Baz int
const (
bazX Baz = iota
bazY
bazZ
)
`,
}
// Test stringer on types defined in different kinds of tests.
// The generated code should not interfere between itself.
func TestTestFiles(t *testing.T) {
testenv.NeedsTool(t, "go")
stringer := stringerPath(t)
dir := t.TempDir()
t.Logf("TestTestFiles in: %s \n", dir)
for name, src := range testfileSrcs {
source := filepath.Join(dir, name)
err := os.WriteFile(source, []byte(src), 0666)
if err != nil {
t.Fatalf("write file: %s", err)
}
}
// Must run stringer in the temp directory, see TestTags.
err := runInDir(t, dir, stringer, "-type=Foo,Bar,Baz", dir)
if err != nil {
t.Fatalf("run stringer: %s", err)
}
// Check that stringer has created the expected files.
content, err := os.ReadDir(dir)
if err != nil {
t.Fatalf("read dir: %s", err)
}
gotFiles := []string{}
for _, f := range content {
if !f.IsDir() {
gotFiles = append(gotFiles, f.Name())
}
}
wantFiles := []string{
// Original.
"go.mod",
"main.go",
"main_test.go",
"main_pkg_test.go",
// Generated.
"foo_string.go",
"bar_string_test.go",
"baz_string_test.go",
}
slices.Sort(gotFiles)
slices.Sort(wantFiles)
if !reflect.DeepEqual(gotFiles, wantFiles) {
t.Errorf("stringer generated files:\n%s\n\nbut want:\n%s",
strings.Join(gotFiles, "\n"),
strings.Join(wantFiles, "\n"),
)
}
// Run go test as a smoke test.
err = runInDir(t, dir, "go", "test", "-count=1", ".")
if err != nil {
t.Fatalf("go test: %s", err)
}
}
// The -output flag cannot be used in combination with matching types across multiple packages.
func TestCollidingOutput(t *testing.T) {
testenv.NeedsTool(t, "go")
stringer := stringerPath(t)
dir := t.TempDir()
for name, src := range testfileSrcs {
source := filepath.Join(dir, name)
err := os.WriteFile(source, []byte(src), 0666)
if err != nil {
t.Fatalf("write file: %s", err)
}
}
// Must run stringer in the temp directory, see TestTags.
err := runInDir(t, dir, stringer, "-type=Foo,Bar,Baz", "-output=somefile.go", dir)
if err == nil {
t.Fatal("unexpected stringer success")
}
}
var exe struct {
path string
err error
once sync.Once
}
func stringerPath(t *testing.T) string {
testenv.NeedsExec(t)
exe.once.Do(func() {
exe.path, exe.err = os.Executable()
})
if exe.err != nil {
t.Fatal(exe.err)
}
return exe.path
}
// stringerCompileAndRun runs stringer for the named file and compiles and
// runs the target binary in directory dir. That binary will panic if the String method is incorrect.
func stringerCompileAndRun(t *testing.T, dir, stringer, typeName, fileName string) {
t.Logf("run: %s %s\n", fileName, typeName)
source := filepath.Join(dir, path.Base(fileName))
err := copy(source, filepath.Join("testdata", fileName))
if err != nil {
t.Fatalf("copying file to temporary directory: %s", err)
}
stringSource := filepath.Join(dir, typeName+"_string.go")
// Run stringer in temporary directory.
err = run(t, stringer, "-type", typeName, "-output", stringSource, source)
if err != nil {
t.Fatal(err)
}
// Run the binary in the temporary directory.
err = run(t, "go", "run", stringSource, source)
if err != nil {
t.Fatal(err)
}
}
// copy copies the from file to the to file.
func copy(to, from string) error {
toFd, err := os.Create(to)
if err != nil {
return err
}
defer toFd.Close()
fromFd, err := os.Open(from)
if err != nil {
return err
}
defer fromFd.Close()
_, err = io.Copy(toFd, fromFd)
return err
}
// run runs a single command and returns an error if it does not succeed.
// os/exec should have this function, to be honest.
func run(t testing.TB, name string, arg ...string) error {
t.Helper()
return runInDir(t, ".", name, arg...)
}
// runInDir runs a single command in directory dir and returns an error if
// it does not succeed.
func runInDir(t testing.TB, dir, name string, arg ...string) error {
t.Helper()
cmd := testenv.Command(t, name, arg...)
cmd.Dir = dir
out, err := cmd.CombinedOutput()
if len(out) > 0 {
t.Logf("%s", out)
}
if err != nil {
return fmt.Errorf("%v: %v", cmd, err)
}
return nil
}
================================================
FILE: cmd/stringer/golden_test.go
================================================
// Copyright 2014 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// This file contains simple golden tests for various examples.
// Besides validating the results when the implementation changes,
// it provides a way to look at the generated code without having
// to execute the print statements in one's head.
package main
import (
"os"
"path/filepath"
"strings"
"testing"
"golang.org/x/tools/internal/testenv"
)
// Golden represents a test case.
type Golden struct {
name string
trimPrefix string
lineComment bool
input string // input; the package clause is provided when running the test.
output string // expected output.
}
var golden = []Golden{
{"day", "", false, day_in, day_out},
{"offset", "", false, offset_in, offset_out},
{"gap", "", false, gap_in, gap_out},
{"num", "", false, num_in, num_out},
{"unum", "", false, unum_in, unum_out},
{"unumpos", "", false, unumpos_in, unumpos_out},
{"prime", "", false, prime_in, prime_out},
{"prefix", "Type", false, prefix_in, prefix_out},
{"tokens", "", true, tokens_in, tokens_out},
{"overflow8", "", false, overflow8_in, overflow8_out},
}
// Each example starts with "type XXX [u]int", with a single space separating them.
// Simple test: enumeration of type int starting at 0.
const day_in = `type Day int
const (
Monday Day = iota
Tuesday
Wednesday
Thursday
Friday
Saturday
Sunday
)
`
const day_out = `func _() {
// An "invalid array index" compiler error signifies that the constant values have changed.
// Re-run the stringer command to generate them again.
var x [1]struct{}
_ = x[Monday-0]
_ = x[Tuesday-1]
_ = x[Wednesday-2]
_ = x[Thursday-3]
_ = x[Friday-4]
_ = x[Saturday-5]
_ = x[Sunday-6]
}
const _Day_name = "MondayTuesdayWednesdayThursdayFridaySaturdaySunday"
var _Day_index = [...]uint8{0, 6, 13, 22, 30, 36, 44, 50}
func (i Day) String() string {
idx := int(i) - 0
if i < 0 || idx >= len(_Day_index)-1 {
return "Day(" + strconv.FormatInt(int64(i), 10) + ")"
}
return _Day_name[_Day_index[idx]:_Day_index[idx+1]]
}
`
// Enumeration with an offset.
// Also includes a duplicate.
const offset_in = `type Number int
const (
_ Number = iota
One
Two
Three
AnotherOne = One // Duplicate; note that AnotherOne doesn't appear below.
)
`
const offset_out = `func _() {
// An "invalid array index" compiler error signifies that the constant values have changed.
// Re-run the stringer command to generate them again.
var x [1]struct{}
_ = x[One-1]
_ = x[Two-2]
_ = x[Three-3]
}
const _Number_name = "OneTwoThree"
var _Number_index = [...]uint8{0, 3, 6, 11}
func (i Number) String() string {
idx := int(i) - 1
if i < 1 || idx >= len(_Number_index)-1 {
return "Number(" + strconv.FormatInt(int64(i), 10) + ")"
}
return _Number_name[_Number_index[idx]:_Number_index[idx+1]]
}
`
// Gaps and an offset.
const gap_in = `type Gap int
const (
Two Gap = 2
Three Gap = 3
Five Gap = 5
Six Gap = 6
Seven Gap = 7
Eight Gap = 8
Nine Gap = 9
Eleven Gap = 11
)
`
const gap_out = `func _() {
// An "invalid array index" compiler error signifies that the constant values have changed.
// Re-run the stringer command to generate them again.
var x [1]struct{}
_ = x[Two-2]
_ = x[Three-3]
_ = x[Five-5]
_ = x[Six-6]
_ = x[Seven-7]
_ = x[Eight-8]
_ = x[Nine-9]
_ = x[Eleven-11]
}
const (
_Gap_name_0 = "TwoThree"
_Gap_name_1 = "FiveSixSevenEightNine"
_Gap_name_2 = "Eleven"
)
var (
_Gap_index_0 = [...]uint8{0, 3, 8}
_Gap_index_1 = [...]uint8{0, 4, 7, 12, 17, 21}
)
func (i Gap) String() string {
switch {
case 2 <= i && i <= 3:
i -= 2
return _Gap_name_0[_Gap_index_0[i]:_Gap_index_0[i+1]]
case 5 <= i && i <= 9:
i -= 5
return _Gap_name_1[_Gap_index_1[i]:_Gap_index_1[i+1]]
case i == 11:
return _Gap_name_2
default:
return "Gap(" + strconv.FormatInt(int64(i), 10) + ")"
}
}
`
// Signed integers spanning zero.
const num_in = `type Num int
const (
m_2 Num = -2 + iota
m_1
m0
m1
m2
)
`
const num_out = `func _() {
// An "invalid array index" compiler error signifies that the constant values have changed.
// Re-run the stringer command to generate them again.
var x [1]struct{}
_ = x[m_2 - -2]
_ = x[m_1 - -1]
_ = x[m0-0]
_ = x[m1-1]
_ = x[m2-2]
}
const _Num_name = "m_2m_1m0m1m2"
var _Num_index = [...]uint8{0, 3, 6, 8, 10, 12}
func (i Num) String() string {
idx := int(i) - -2
if i < -2 || idx >= len(_Num_index)-1 {
return "Num(" + strconv.FormatInt(int64(i), 10) + ")"
}
return _Num_name[_Num_index[idx]:_Num_index[idx+1]]
}
`
// Unsigned integers spanning zero.
const unum_in = `type Unum uint
const (
m_2 Unum = iota + 253
m_1
)
const (
m0 Unum = iota
m1
m2
)
`
const unum_out = `func _() {
// An "invalid array index" compiler error signifies that the constant values have changed.
// Re-run the stringer command to generate them again.
var x [1]struct{}
_ = x[m_2-253]
_ = x[m_1-254]
_ = x[m0-0]
_ = x[m1-1]
_ = x[m2-2]
}
const (
_Unum_name_0 = "m0m1m2"
_Unum_name_1 = "m_2m_1"
)
var (
_Unum_index_0 = [...]uint8{0, 2, 4, 6}
_Unum_index_1 = [...]uint8{0, 3, 6}
)
func (i Unum) String() string {
switch {
case i <= 2:
return _Unum_name_0[_Unum_index_0[i]:_Unum_index_0[i+1]]
case 253 <= i && i <= 254:
i -= 253
return _Unum_name_1[_Unum_index_1[i]:_Unum_index_1[i+1]]
default:
return "Unum(" + strconv.FormatInt(int64(i), 10) + ")"
}
}
`
// Unsigned positive integers.
const unumpos_in = `type Unumpos uint
const (
m253 Unumpos = iota + 253
m254
)
const (
m1 Unumpos = iota + 1
m2
m3
)
`
const unumpos_out = `func _() {
// An "invalid array index" compiler error signifies that the constant values have changed.
// Re-run the stringer command to generate them again.
var x [1]struct{}
_ = x[m253-253]
_ = x[m254-254]
_ = x[m1-1]
_ = x[m2-2]
_ = x[m3-3]
}
const (
_Unumpos_name_0 = "m1m2m3"
_Unumpos_name_1 = "m253m254"
)
var (
_Unumpos_index_0 = [...]uint8{0, 2, 4, 6}
_Unumpos_index_1 = [...]uint8{0, 4, 8}
)
func (i Unumpos) String() string {
switch {
case 1 <= i && i <= 3:
i -= 1
return _Unumpos_name_0[_Unumpos_index_0[i]:_Unumpos_index_0[i+1]]
case 253 <= i && i <= 254:
i -= 253
return _Unumpos_name_1[_Unumpos_index_1[i]:_Unumpos_index_1[i+1]]
default:
return "Unumpos(" + strconv.FormatInt(int64(i), 10) + ")"
}
}
`
// Enough gaps to trigger a map implementation of the method.
// Also includes a duplicate to test that it doesn't cause problems
const prime_in = `type Prime int
const (
p2 Prime = 2
p3 Prime = 3
p5 Prime = 5
p7 Prime = 7
p77 Prime = 7 // Duplicate; note that p77 doesn't appear below.
p11 Prime = 11
p13 Prime = 13
p17 Prime = 17
p19 Prime = 19
p23 Prime = 23
p29 Prime = 29
p37 Prime = 31
p41 Prime = 41
p43 Prime = 43
)
`
const prime_out = `func _() {
// An "invalid array index" compiler error signifies that the constant values have changed.
// Re-run the stringer command to generate them again.
var x [1]struct{}
_ = x[p2-2]
_ = x[p3-3]
_ = x[p5-5]
_ = x[p7-7]
_ = x[p77-7]
_ = x[p11-11]
_ = x[p13-13]
_ = x[p17-17]
_ = x[p19-19]
_ = x[p23-23]
_ = x[p29-29]
_ = x[p37-31]
_ = x[p41-41]
_ = x[p43-43]
}
const _Prime_name = "p2p3p5p7p11p13p17p19p23p29p37p41p43"
var _Prime_map = map[Prime]string{
2: _Prime_name[0:2],
3: _Prime_name[2:4],
5: _Prime_name[4:6],
7: _Prime_name[6:8],
11: _Prime_name[8:11],
13: _Prime_name[11:14],
17: _Prime_name[14:17],
19: _Prime_name[17:20],
23: _Prime_name[20:23],
29: _Prime_name[23:26],
31: _Prime_name[26:29],
41: _Prime_name[29:32],
43: _Prime_name[32:35],
}
func (i Prime) String() string {
if str, ok := _Prime_map[i]; ok {
return str
}
return "Prime(" + strconv.FormatInt(int64(i), 10) + ")"
}
`
const prefix_in = `type Type int
const (
TypeInt Type = iota
TypeString
TypeFloat
TypeRune
TypeByte
TypeStruct
TypeSlice
)
`
const prefix_out = `func _() {
// An "invalid array index" compiler error signifies that the constant values have changed.
// Re-run the stringer command to generate them again.
var x [1]struct{}
_ = x[TypeInt-0]
_ = x[TypeString-1]
_ = x[TypeFloat-2]
_ = x[TypeRune-3]
_ = x[TypeByte-4]
_ = x[TypeStruct-5]
_ = x[TypeSlice-6]
}
const _Type_name = "IntStringFloatRuneByteStructSlice"
var _Type_index = [...]uint8{0, 3, 9, 14, 18, 22, 28, 33}
func (i Type) String() string {
idx := int(i) - 0
if i < 0 || idx >= len(_Type_index)-1 {
return "Type(" + strconv.FormatInt(int64(i), 10) + ")"
}
return _Type_name[_Type_index[idx]:_Type_index[idx+1]]
}
`
const tokens_in = `type Token int
const (
And Token = iota // &
Or // |
Add // +
Sub // -
Ident
Period // .
// not to be used
SingleBefore
// not to be used
BeforeAndInline // inline
InlineGeneral /* inline general */
)
`
const tokens_out = `func _() {
// An "invalid array index" compiler error signifies that the constant values have changed.
// Re-run the stringer command to generate them again.
var x [1]struct{}
_ = x[And-0]
_ = x[Or-1]
_ = x[Add-2]
_ = x[Sub-3]
_ = x[Ident-4]
_ = x[Period-5]
_ = x[SingleBefore-6]
_ = x[BeforeAndInline-7]
_ = x[InlineGeneral-8]
}
const _Token_name = "&|+-Ident.SingleBeforeinlineinline general"
var _Token_index = [...]uint8{0, 1, 2, 3, 4, 9, 10, 22, 28, 42}
func (i Token) String() string {
idx := int(i) - 0
if i < 0 || idx >= len(_Token_index)-1 {
return "Token(" + strconv.FormatInt(int64(i), 10) + ")"
}
return _Token_name[_Token_index[idx]:_Token_index[idx+1]]
}
`
const overflow8_in = `type Overflow8 int8
const (
O_128 Overflow8 = -128
O_127 Overflow8 = -127
O_126 Overflow8 = -126
O_125 Overflow8 = -125
O_124 Overflow8 = -124
O_123 Overflow8 = -123
O_122 Overflow8 = -122
O_121 Overflow8 = -121
O_120 Overflow8 = -120
O_119 Overflow8 = -119
O_118 Overflow8 = -118
O_117 Overflow8 = -117
O_116 Overflow8 = -116
O_115 Overflow8 = -115
O_114 Overflow8 = -114
O_113 Overflow8 = -113
O_112 Overflow8 = -112
O_111 Overflow8 = -111
O_110 Overflow8 = -110
O_109 Overflow8 = -109
O_108 Overflow8 = -108
O_107 Overflow8 = -107
O_106 Overflow8 = -106
O_105 Overflow8 = -105
O_104 Overflow8 = -104
O_103 Overflow8 = -103
O_102 Overflow8 = -102
O_101 Overflow8 = -101
O_100 Overflow8 = -100
O_99 Overflow8 = -99
O_98 Overflow8 = -98
O_97 Overflow8 = -97
O_96 Overflow8 = -96
O_95 Overflow8 = -95
O_94 Overflow8 = -94
O_93 Overflow8 = -93
O_92 Overflow8 = -92
O_91 Overflow8 = -91
O_90 Overflow8 = -90
O_89 Overflow8 = -89
O_88 Overflow8 = -88
O_87 Overflow8 = -87
O_86 Overflow8 = -86
O_85 Overflow8 = -85
O_84 Overflow8 = -84
O_83 Overflow8 = -83
O_82 Overflow8 = -82
O_81 Overflow8 = -81
O_80 Overflow8 = -80
O_79 Overflow8 = -79
O_78 Overflow8 = -78
O_77 Overflow8 = -77
O_76 Overflow8 = -76
O_75 Overflow8 = -75
O_74 Overflow8 = -74
O_73 Overflow8 = -73
O_72 Overflow8 = -72
O_71 Overflow8 = -71
O_70 Overflow8 = -70
O_69 Overflow8 = -69
O_68 Overflow8 = -68
O_67 Overflow8 = -67
O_66 Overflow8 = -66
O_65 Overflow8 = -65
O_64 Overflow8 = -64
O_63 Overflow8 = -63
O_62 Overflow8 = -62
O_61 Overflow8 = -61
O_60 Overflow8 = -60
O_59 Overflow8 = -59
O_58 Overflow8 = -58
O_57 Overflow8 = -57
O_56 Overflow8 = -56
O_55 Overflow8 = -55
O_54 Overflow8 = -54
O_53 Overflow8 = -53
O_52 Overflow8 = -52
O_51 Overflow8 = -51
O_50 Overflow8 = -50
O_49 Overflow8 = -49
O_48 Overflow8 = -48
O_47 Overflow8 = -47
O_46 Overflow8 = -46
O_45 Overflow8 = -45
O_44 Overflow8 = -44
O_43 Overflow8 = -43
O_42 Overflow8 = -42
O_41 Overflow8 = -41
O_40 Overflow8 = -40
O_39 Overflow8 = -39
O_38 Overflow8 = -38
O_37 Overflow8 = -37
O_36 Overflow8 = -36
O_35 Overflow8 = -35
O_34 Overflow8 = -34
O_33 Overflow8 = -33
O_32 Overflow8 = -32
O_31 Overflow8 = -31
O_30 Overflow8 = -30
O_29 Overflow8 = -29
O_28 Overflow8 = -28
O_27 Overflow8 = -27
O_26 Overflow8 = -26
O_25 Overflow8 = -25
O_24 Overflow8 = -24
O_23 Overflow8 = -23
O_22 Overflow8 = -22
O_21 Overflow8 = -21
O_20 Overflow8 = -20
O_19 Overflow8 = -19
O_18 Overflow8 = -18
O_17 Overflow8 = -17
O_16 Overflow8 = -16
O_15 Overflow8 = -15
O_14 Overflow8 = -14
O_13 Overflow8 = -13
O_12 Overflow8 = -12
O_11 Overflow8 = -11
O_10 Overflow8 = -10
O_9 Overflow8 = -9
O_8 Overflow8 = -8
O_7 Overflow8 = -7
O_6 Overflow8 = -6
O_5 Overflow8 = -5
O_4 Overflow8 = -4
O_3 Overflow8 = -3
O_2 Overflow8 = -2
O_1 Overflow8 = -1
O0 Overflow8 = 0
O1 Overflow8 = 1
O2 Overflow8 = 2
O3 Overflow8 = 3
O4 Overflow8 = 4
O5 Overflow8 = 5
O6 Overflow8 = 6
O7 Overflow8 = 7
O8 Overflow8 = 8
O9 Overflow8 = 9
O10 Overflow8 = 10
O11 Overflow8 = 11
O12 Overflow8 = 12
O13 Overflow8 = 13
O14 Overflow8 = 14
O15 Overflow8 = 15
O16 Overflow8 = 16
O17 Overflow8 = 17
O18 Overflow8 = 18
O19 Overflow8 = 19
O20 Overflow8 = 20
O21 Overflow8 = 21
O22 Overflow8 = 22
O23 Overflow8 = 23
O24 Overflow8 = 24
O25 Overflow8 = 25
O26 Overflow8 = 26
O27 Overflow8 = 27
O28 Overflow8 = 28
O29 Overflow8 = 29
O30 Overflow8 = 30
O31 Overflow8 = 31
O32 Overflow8 = 32
O33 Overflow8 = 33
O34 Overflow8 = 34
O35 Overflow8 = 35
O36 Overflow8 = 36
O37 Overflow8 = 37
O38 Overflow8 = 38
O39 Overflow8 = 39
O40 Overflow8 = 40
O41 Overflow8 = 41
O42 Overflow8 = 42
O43 Overflow8 = 43
O44 Overflow8 = 44
O45 Overflow8 = 45
O46 Overflow8 = 46
O47 Overflow8 = 47
O48 Overflow8 = 48
O49 Overflow8 = 49
O50 Overflow8 = 50
O51 Overflow8 = 51
O52 Overflow8 = 52
O53 Overflow8 = 53
O54 Overflow8 = 54
O55 Overflow8 = 55
O56 Overflow8 = 56
O57 Overflow8 = 57
O58 Overflow8 = 58
O59 Overflow8 = 59
O60 Overflow8 = 60
O61 Overflow8 = 61
O62 Overflow8 = 62
O63 Overflow8 = 63
O64 Overflow8 = 64
O65 Overflow8 = 65
O66 Overflow8 = 66
O67 Overflow8 = 67
O68 Overflow8 = 68
O69 Overflow8 = 69
O70 Overflow8 = 70
O71 Overflow8 = 71
O72 Overflow8 = 72
O73 Overflow8 = 73
O74 Overflow8 = 74
O75 Overflow8 = 75
O76 Overflow8 = 76
O77 Overflow8 = 77
O78 Overflow8 = 78
O79 Overflow8 = 79
O80 Overflow8 = 80
O81 Overflow8 = 81
O82 Overflow8 = 82
O83 Overflow8 = 83
O84 Overflow8 = 84
O85 Overflow8 = 85
O86 Overflow8 = 86
O87 Overflow8 = 87
O88 Overflow8 = 88
O89 Overflow8 = 89
O90 Overflow8 = 90
O91 Overflow8 = 91
O92 Overflow8 = 92
O93 Overflow8 = 93
O94 Overflow8 = 94
O95 Overflow8 = 95
O96 Overflow8 = 96
O97 Overflow8 = 97
O98 Overflow8 = 98
O99 Overflow8 = 99
O100 Overflow8 = 100
O101 Overflow8 = 101
O102 Overflow8 = 102
O103 Overflow8 = 103
O104 Overflow8 = 104
O105 Overflow8 = 105
O106 Overflow8 = 106
O107 Overflow8 = 107
O108 Overflow8 = 108
O109 Overflow8 = 109
O110 Overflow8 = 110
O111 Overflow8 = 111
O112 Overflow8 = 112
O113 Overflow8 = 113
O114 Overflow8 = 114
O115 Overflow8 = 115
O116 Overflow8 = 116
O117 Overflow8 = 117
O118 Overflow8 = 118
O119 Overflow8 = 119
O120 Overflow8 = 120
O121 Overflow8 = 121
O122 Overflow8 = 122
O123 Overflow8 = 123
O124 Overflow8 = 124
O125 Overflow8 = 125
O126 Overflow8 = 126
O127 Overflow8 = 127
)
`
const overflow8_out = `func _() {
// An "invalid array index" compiler error signifies that the constant values have changed.
// Re-run the stringer command to generate them again.
var x [1]struct{}
_ = x[O_128 - -128]
_ = x[O_127 - -127]
_ = x[O_126 - -126]
_ = x[O_125 - -125]
_ = x[O_124 - -124]
_ = x[O_123 - -123]
_ = x[O_122 - -122]
_ = x[O_121 - -121]
_ = x[O_120 - -120]
_ = x[O_119 - -119]
_ = x[O_118 - -118]
_ = x[O_117 - -117]
_ = x[O_116 - -116]
_ = x[O_115 - -115]
_ = x[O_114 - -114]
_ = x[O_113 - -113]
_ = x[O_112 - -112]
_ = x[O_111 - -111]
_ = x[O_110 - -110]
_ = x[O_109 - -109]
_ = x[O_108 - -108]
_ = x[O_107 - -107]
_ = x[O_106 - -106]
_ = x[O_105 - -105]
_ = x[O_104 - -104]
_ = x[O_103 - -103]
_ = x[O_102 - -102]
_ = x[O_101 - -101]
_ = x[O_100 - -100]
_ = x[O_99 - -99]
_ = x[O_98 - -98]
_ = x[O_97 - -97]
_ = x[O_96 - -96]
_ = x[O_95 - -95]
_ = x[O_94 - -94]
_ = x[O_93 - -93]
_ = x[O_92 - -92]
_ = x[O_91 - -91]
_ = x[O_90 - -90]
_ = x[O_89 - -89]
_ = x[O_88 - -88]
_ = x[O_87 - -87]
_ = x[O_86 - -86]
_ = x[O_85 - -85]
_ = x[O_84 - -84]
_ = x[O_83 - -83]
_ = x[O_82 - -82]
_ = x[O_81 - -81]
_ = x[O_80 - -80]
_ = x[O_79 - -79]
_ = x[O_78 - -78]
_ = x[O_77 - -77]
_ = x[O_76 - -76]
_ = x[O_75 - -75]
_ = x[O_74 - -74]
_ = x[O_73 - -73]
_ = x[O_72 - -72]
_ = x[O_71 - -71]
_ = x[O_70 - -70]
_ = x[O_69 - -69]
_ = x[O_68 - -68]
_ = x[O_67 - -67]
_ = x[O_66 - -66]
_ = x[O_65 - -65]
_ = x[O_64 - -64]
_ = x[O_63 - -63]
_ = x[O_62 - -62]
_ = x[O_61 - -61]
_ = x[O_60 - -60]
_ = x[O_59 - -59]
_ = x[O_58 - -58]
_ = x[O_57 - -57]
_ = x[O_56 - -56]
_ = x[O_55 - -55]
_ = x[O_54 - -54]
_ = x[O_53 - -53]
_ = x[O_52 - -52]
_ = x[O_51 - -51]
_ = x[O_50 - -50]
_ = x[O_49 - -49]
_ = x[O_48 - -48]
_ = x[O_47 - -47]
_ = x[O_46 - -46]
_ = x[O_45 - -45]
_ = x[O_44 - -44]
_ = x[O_43 - -43]
_ = x[O_42 - -42]
_ = x[O_41 - -41]
_ = x[O_40 - -40]
_ = x[O_39 - -39]
_ = x[O_38 - -38]
_ = x[O_37 - -37]
_ = x[O_36 - -36]
_ = x[O_35 - -35]
_ = x[O_34 - -34]
_ = x[O_33 - -33]
_ = x[O_32 - -32]
_ = x[O_31 - -31]
_ = x[O_30 - -30]
_ = x[O_29 - -29]
_ = x[O_28 - -28]
_ = x[O_27 - -27]
_ = x[O_26 - -26]
_ = x[O_25 - -25]
_ = x[O_24 - -24]
_ = x[O_23 - -23]
_ = x[O_22 - -22]
_ = x[O_21 - -21]
_ = x[O_20 - -20]
_ = x[O_19 - -19]
_ = x[O_18 - -18]
_ = x[O_17 - -17]
_ = x[O_16 - -16]
_ = x[O_15 - -15]
_ = x[O_14 - -14]
_ = x[O_13 - -13]
_ = x[O_12 - -12]
_ = x[O_11 - -11]
_ = x[O_10 - -10]
_ = x[O_9 - -9]
_ = x[O_8 - -8]
_ = x[O_7 - -7]
_ = x[O_6 - -6]
_ = x[O_5 - -5]
_ = x[O_4 - -4]
_ = x[O_3 - -3]
_ = x[O_2 - -2]
_ = x[O_1 - -1]
_ = x[O0-0]
_ = x[O1-1]
_ = x[O2-2]
_ = x[O3-3]
_ = x[O4-4]
_ = x[O5-5]
_ = x[O6-6]
_ = x[O7-7]
_ = x[O8-8]
_ = x[O9-9]
_ = x[O10-10]
_ = x[O11-11]
_ = x[O12-12]
_ = x[O13-13]
_ = x[O14-14]
_ = x[O15-15]
_ = x[O16-16]
_ = x[O17-17]
_ = x[O18-18]
_ = x[O19-19]
_ = x[O20-20]
_ = x[O21-21]
_ = x[O22-22]
_ = x[O23-23]
_ = x[O24-24]
_ = x[O25-25]
_ = x[O26-26]
_ = x[O27-27]
_ = x[O28-28]
_ = x[O29-29]
_ = x[O30-30]
_ = x[O31-31]
_ = x[O32-32]
_ = x[O33-33]
_ = x[O34-34]
_ = x[O35-35]
_ = x[O36-36]
_ = x[O37-37]
_ = x[O38-38]
_ = x[O39-39]
_ = x[O40-40]
_ = x[O41-41]
_ = x[O42-42]
_ = x[O43-43]
_ = x[O44-44]
_ = x[O45-45]
_ = x[O46-46]
_ = x[O47-47]
_ = x[O48-48]
_ = x[O49-49]
_ = x[O50-50]
_ = x[O51-51]
_ = x[O52-52]
_ = x[O53-53]
_ = x[O54-54]
_ = x[O55-55]
_ = x[O56-56]
_ = x[O57-57]
_ = x[O58-58]
_ = x[O59-59]
_ = x[O60-60]
_ = x[O61-61]
_ = x[O62-62]
_ = x[O63-63]
_ = x[O64-64]
_ = x[O65-65]
_ = x[O66-66]
_ = x[O67-67]
_ = x[O68-68]
_ = x[O69-69]
_ = x[O70-70]
_ = x[O71-71]
_ = x[O72-72]
_ = x[O73-73]
_ = x[O74-74]
_ = x[O75-75]
_ = x[O76-76]
_ = x[O77-77]
_ = x[O78-78]
_ = x[O79-79]
_ = x[O80-80]
_ = x[O81-81]
_ = x[O82-82]
_ = x[O83-83]
_ = x[O84-84]
_ = x[O85-85]
_ = x[O86-86]
_ = x[O87-87]
_ = x[O88-88]
_ = x[O89-89]
_ = x[O90-90]
_ = x[O91-91]
_ = x[O92-92]
_ = x[O93-93]
_ = x[O94-94]
_ = x[O95-95]
_ = x[O96-96]
_ = x[O97-97]
_ = x[O98-98]
_ = x[O99-99]
_ = x[O100-100]
_ = x[O101-101]
_ = x[O102-102]
_ = x[O103-103]
_ = x[O104-104]
_ = x[O105-105]
_ = x[O106-106]
_ = x[O107-107]
_ = x[O108-108]
_ = x[O109-109]
_ = x[O110-110]
_ = x[O111-111]
_ = x[O112-112]
_ = x[O113-113]
_ = x[O114-114]
_ = x[O115-115]
_ = x[O116-116]
_ = x[O117-117]
_ = x[O118-118]
_ = x[O119-119]
_ = x[O120-120]
_ = x[O121-121]
_ = x[O122-122]
_ = x[O123-123]
_ = x[O124-124]
_ = x[O125-125]
_ = x[O126-126]
_ = x[O127-127]
}
const _Overflow8_name = "O_128O_127O_126O_125O_124O_123O_122O_121O_120O_119O_118O_117O_116O_115O_114O_113O_112O_111O_110O_109O_108O_107O_106O_105O_104O_103O_102O_101O_100O_99O_98O_97O_96O_95O_94O_93O_92O_91O_90O_89O_88O_87O_86O_85O_84O_83O_82O_81O_80O_79O_78O_77O_76O_75O_74O_73O_72O_71O_70O_69O_68O_67O_66O_65O_64O_63O_62O_61O_60O_59O_58O_57O_56O_55O_54O_53O_52O_51O_50O_49O_48O_47O_46O_45O_44O_43O_42O_41O_40O_39O_38O_37O_36O_35O_34O_33O_32O_31O_30O_29O_28O_27O_26O_25O_24O_23O_22O_21O_20O_19O_18O_17O_16O_15O_14O_13O_12O_11O_10O_9O_8O_7O_6O_5O_4O_3O_2O_1O0O1O2O3O4O5O6O7O8O9O10O11O12O13O14O15O16O17O18O19O20O21O22O23O24O25O26O27O28O29O30O31O32O33O34O35O36O37O38O39O40O41O42O43O44O45O46O47O48O49O50O51O52O53O54O55O56O57O58O59O60O61O62O63O64O65O66O67O68O69O70O71O72O73O74O75O76O77O78O79O80O81O82O83O84O85O86O87O88O89O90O91O92O93O94O95O96O97O98O99O100O101O102O103O104O105O106O107O108O109O110O111O112O113O114O115O116O117O118O119O120O121O122O123O124O125O126O127"
var _Overflow8_index = [...]uint16{0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60, 65, 70, 75, 80, 85, 90, 95, 100, 105, 110, 115, 120, 125, 130, 135, 140, 145, 149, 153, 157, 161, 165, 169, 173, 177, 181, 185, 189, 193, 197, 201, 205, 209, 213, 217, 221, 225, 229, 233, 237, 241, 245, 249, 253, 257, 261, 265, 269, 273, 277, 281, 285, 289, 293, 297, 301, 305, 309, 313, 317, 321, 325, 329, 333, 337, 341, 345, 349, 353, 357, 361, 365, 369, 373, 377, 381, 385, 389, 393, 397, 401, 405, 409, 413, 417, 421, 425, 429, 433, 437, 441, 445, 449, 453, 457, 461, 465, 469, 473, 477, 481, 485, 489, 493, 497, 501, 505, 508, 511, 514, 517, 520, 523, 526, 529, 532, 534, 536, 538, 540, 542, 544, 546, 548, 550, 552, 555, 558, 561, 564, 567, 570, 573, 576, 579, 582, 585, 588, 591, 594, 597, 600, 603, 606, 609, 612, 615, 618, 621, 624, 627, 630, 633, 636, 639, 642, 645, 648, 651, 654, 657, 660, 663, 666, 669, 672, 675, 678, 681, 684, 687, 690, 693, 696, 699, 702, 705, 708, 711, 714, 717, 720, 723, 726, 729, 732, 735, 738, 741, 744, 747, 750, 753, 756, 759, 762, 765, 768, 771, 774, 777, 780, 783, 786, 789, 792, 795, 798, 801, 804, 807, 810, 813, 816, 819, 822, 826, 830, 834, 838, 842, 846, 850, 854, 858, 862, 866, 870, 874, 878, 882, 886, 890, 894, 898, 902, 906, 910, 914, 918, 922, 926, 930, 934}
func (i Overflow8) String() string {
idx := int(i) - -128
if i < -128 || idx >= len(_Overflow8_index)-1 {
return "Overflow8(" + strconv.FormatInt(int64(i), 10) + ")"
}
return _Overflow8_name[_Overflow8_index[idx]:_Overflow8_index[idx+1]]
}
`
func TestGolden(t *testing.T) {
testenv.NeedsTool(t, "go")
dir := t.TempDir()
for _, test := range golden {
t.Run(test.name, func(t *testing.T) {
input := "package test\n" + test.input
file := test.name + ".go"
absFile := filepath.Join(dir, file)
err := os.WriteFile(absFile, []byte(input), 0644)
if err != nil {
t.Fatal(err)
}
pkgs := loadPackages([]string{absFile}, nil, test.trimPrefix, test.lineComment, t.Logf)
if len(pkgs) != 1 {
t.Fatalf("got %d parsed packages but expected 1", len(pkgs))
}
// Extract the name and type of the constant from the first line.
tokens := strings.SplitN(test.input, " ", 3)
if len(tokens) != 3 {
t.Fatalf("%s: need type declaration on first line", test.name)
}
g := Generator{
pkg: pkgs[0],
logf: t.Logf,
}
g.generate(tokens[1], findValues(tokens[1], pkgs[0]))
got := string(g.format())
if got != test.output {
t.Errorf("%s: got(%d)\n====\n%q====\nexpected(%d)\n====\n%q", test.name, len(got), got, len(test.output), test.output)
}
})
}
}
================================================
FILE: cmd/stringer/multifile_test.go
================================================
// Copyright 2024 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// go1.23 is required for os.CopyFS.
// !android is required for compatibility with endtoend_test.go.
//go:build go1.23 && !android
package main
import (
"bytes"
"errors"
"fmt"
"os"
"path/filepath"
"testing"
"golang.org/x/tools/internal/diffp"
"golang.org/x/tools/internal/testenv"
"golang.org/x/tools/txtar"
)
// This file contains a test that checks the output files existence
// and content when stringer has types from multiple different input
// files to choose from.
//
// Input is specified in a txtar string.
// Several tests expect the type Foo generated in some package.
func expectFooString(pkg string) []byte {
return fmt.Appendf(nil, `
// Header comment ignored.
package %s
import "strconv"
func _() {
// An "invalid array index" compiler error signifies that the constant values have changed.
// Re-run the stringer command to generate them again.
var x [1]struct{}
_ = x[fooX-0]
_ = x[fooY-1]
_ = x[fooZ-2]
}
const _Foo_name = "fooXfooYfooZ"
var _Foo_index = [...]uint8{0, 4, 8, 12}
func (i Foo) String() string {
idx := int(i) - 0
if i < 0 || idx >= len(_Foo_index)-1 {
return "Foo(" + strconv.FormatInt(int64(i), 10) + ")"
}
return _Foo_name[_Foo_index[idx]:_Foo_index[idx+1]]
}`, pkg)
}
func TestMultifileStringer(t *testing.T) {
testenv.NeedsTool(t, "go")
stringer := stringerPath(t)
tests := []struct {
name string
args []string
archive []byte
expectFiles map[string][]byte
}{
{
name: "package only",
args: []string{"-type=Foo"},
archive: []byte(`
-- go.mod --
module foo
-- main.go --
package main
type Foo int
const (
fooX Foo = iota
fooY
fooZ
)`),
expectFiles: map[string][]byte{
"foo_string.go": expectFooString("main"),
},
},
{
name: "test package only",
args: []string{"-type=Foo"},
archive: []byte(`
-- go.mod --
module foo
-- main.go --
package main
func main() {}
-- main_test.go --
package main
type Foo int
const (
fooX Foo = iota
fooY
fooZ
)`),
expectFiles: map[string][]byte{
"foo_string_test.go": expectFooString("main"),
},
},
{
name: "x_test package only",
args: []string{"-type=Foo"},
archive: []byte(`
-- go.mod --
module foo
-- main.go --
package main
func main() {}
-- main_test.go --
package main_test
type Foo int
const (
fooX Foo = iota
fooY
fooZ
)`),
expectFiles: map[string][]byte{
"foo_string_test.go": expectFooString("main_test"),
},
},
{
// Re-declaring the type in a less prioritized package does not change our output.
name: "package over test package",
args: []string{"-type=Foo"},
archive: []byte(`
-- go.mod --
module foo
-- main.go --
package main
type Foo int
const (
fooX Foo = iota
fooY
fooZ
)
-- main_test.go --
package main
type Foo int
const (
otherX Foo = iota
otherY
otherZ
)
`),
expectFiles: map[string][]byte{
"foo_string.go": expectFooString("main"),
},
},
{
// Re-declaring the type in a less prioritized package does not change our output.
name: "package over x_test package",
args: []string{"-type=Foo"},
archive: []byte(`
-- go.mod --
module foo
-- main.go --
package main
type Foo int
const (
fooX Foo = iota
fooY
fooZ
)
-- main_test.go --
package main_test
type Foo int
const (
otherX Foo = iota
otherY
otherZ
)
`),
expectFiles: map[string][]byte{
"foo_string.go": expectFooString("main"),
},
},
{
// Re-declaring the type in a less prioritized package does not change our output.
name: "test package over x_test package",
args: []string{"-type=Foo"},
archive: []byte(`
-- go.mod --
module foo
-- main.go --
package main
-- main_test.go --
package main
type Foo int
const (
fooX Foo = iota
fooY
fooZ
)
-- main_pkg_test.go --
package main_test
type Foo int
const (
otherX Foo = iota
otherY
otherZ
)`),
expectFiles: map[string][]byte{
"foo_string_test.go": expectFooString("main"),
},
},
{
name: "unique type in each package variant",
args: []string{"-type=Foo,Bar,Baz"},
archive: []byte(`
-- go.mod --
module foo
-- main.go --
package main
type Foo int
const fooX Foo = 1
-- main_test.go --
package main
type Bar int
const barX Bar = 1
-- main_pkg_test.go --
package main_test
type Baz int
const bazX Baz = 1
`),
expectFiles: map[string][]byte{
"foo_string.go": []byte(`
// Header comment ignored.
package main
import "strconv"
func _() {
// An "invalid array index" compiler error signifies that the constant values have changed.
// Re-run the stringer command to generate them again.
var x [1]struct{}
_ = x[fooX-1]
}
const _Foo_name = "fooX"
var _Foo_index = [...]uint8{0, 4}
func (i Foo) String() string {
idx := int(i) - 1
if i < 1 || idx >= len(_Foo_index)-1 {
return "Foo(" + strconv.FormatInt(int64(i), 10) + ")"
}
return _Foo_name[_Foo_index[idx]:_Foo_index[idx+1]]
}`),
"bar_string_test.go": []byte(`
// Header comment ignored.
package main
import "strconv"
func _() {
// An "invalid array index" compiler error signifies that the constant values have changed.
// Re-run the stringer command to generate them again.
var x [1]struct{}
_ = x[barX-1]
}
const _Bar_name = "barX"
var _Bar_index = [...]uint8{0, 4}
func (i Bar) String() string {
idx := int(i) - 1
if i < 1 || idx >= len(_Bar_index)-1 {
return "Bar(" + strconv.FormatInt(int64(i), 10) + ")"
}
return _Bar_name[_Bar_index[idx]:_Bar_index[idx+1]]
}`),
"baz_string_test.go": []byte(`
// Header comment ignored.
package main_test
import "strconv"
func _() {
// An "invalid array index" compiler error signifies that the constant values have changed.
// Re-run the stringer command to generate them again.
var x [1]struct{}
_ = x[bazX-1]
}
const _Baz_name = "bazX"
var _Baz_index = [...]uint8{0, 4}
func (i Baz) String() string {
idx := int(i) - 1
if i < 1 || idx >= len(_Baz_index)-1 {
return "Baz(" + strconv.FormatInt(int64(i), 10) + ")"
}
return _Baz_name[_Baz_index[idx]:_Baz_index[idx+1]]
}`),
},
},
{
name: "package over test package with custom output",
args: []string{"-type=Foo", "-output=custom_output.go"},
archive: []byte(`
-- go.mod --
module foo
-- main.go --
package main
type Foo int
const (
fooX Foo = iota
fooY
fooZ
)
-- main_test.go --
package main
type Foo int
const (
otherX Foo = iota
otherY
otherZ
)`),
expectFiles: map[string][]byte{
"custom_output.go": expectFooString("main"),
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
tmpDir := t.TempDir()
arFS, err := txtar.FS(txtar.Parse(tt.archive))
if err != nil {
t.Fatalf("txtar.FS: %s", err)
}
err = os.CopyFS(tmpDir, arFS)
if err != nil {
t.Fatalf("copy fs: %s", err)
}
before := dirContent(t, tmpDir)
// Must run stringer in the temp directory, see TestTags.
args := append(tt.args, tmpDir)
err = runInDir(t, tmpDir, stringer, args...)
if err != nil {
t.Fatalf("run stringer: %s", err)
}
// Check that all !path files have been created with the expected content.
for f, want := range tt.expectFiles {
got, err := os.ReadFile(filepath.Join(tmpDir, f))
if errors.Is(err, os.ErrNotExist) {
t.Errorf("expected file not written during test: %s", f)
continue
}
if err != nil {
t.Fatalf("read file %q: %s", f, err)
}
// Trim data for more robust comparison.
got = trimHeader(bytes.TrimSpace(got))
want = trimHeader(bytes.TrimSpace(want))
if !bytes.Equal(want, got) {
t.Errorf("file %s does not have the expected content:\n%s", f, diffp.Diff("want", want, "got", got))
}
}
// Check that nothing else has been created.
after := dirContent(t, tmpDir)
for f := range after {
if _, expected := tt.expectFiles[f]; !expected && !before[f] {
t.Errorf("found %q in output directory, it is neither input or expected output", f)
}
}
})
}
}
func dirContent(t *testing.T, dir string) map[string]bool {
entries, err := os.ReadDir(dir)
if err != nil {
t.Fatalf("read dir: %s", err)
}
out := map[string]bool{}
for _, e := range entries {
out[e.Name()] = true
}
return out
}
// trimHeader that stringer puts in file.
// It depends on location and interferes with comparing file content.
func trimHeader(s []byte) []byte {
if !bytes.HasPrefix(s, []byte("//")) {
return s
}
_, after, ok := bytes.Cut(s, []byte{'\n'})
if ok {
return after
}
return s
}
================================================
FILE: cmd/stringer/stringer.go
================================================
// Copyright 2014 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Stringer is a tool to automate the creation of methods that satisfy the fmt.Stringer
// interface. Given the name of a (signed or unsigned) integer type T that has constants
// defined, stringer will create a new self-contained Go source file implementing
//
// func (t T) String() string
//
// The file is created in the same package and directory as the package that defines T.
// It has helpful defaults designed for use with go generate.
//
// Stringer works best with constants that are consecutive values such as created using iota,
// but creates good code regardless. In the future it might also provide custom support for
// constant sets that are bit patterns.
//
// For example, given this snippet,
//
// package painkiller
//
// type Pill int
//
// const (
// Placebo Pill = iota
// Aspirin
// Ibuprofen
// Paracetamol
// Acetaminophen = Paracetamol
// )
//
// running this command
//
// stringer -type=Pill
//
// in the same directory will create the file pill_string.go, in package painkiller,
// containing a definition of
//
// func (Pill) String() string
//
// That method will translate the value of a Pill constant to the string representation
// of the respective constant name, so that the call fmt.Print(painkiller.Aspirin) will
// print the string "Aspirin".
//
// Typically this process would be run using go generate, like this:
//
// //go:generate stringer -type=Pill
//
// If multiple constants have the same value, the lexically first matching name will
// be used (in the example, Acetaminophen will print as "Paracetamol").
//
// With no arguments, it processes the package in the current directory.
// Otherwise, the arguments must name a single directory holding a Go package
// or a set of Go source files that represent a single Go package.
//
// The -type flag accepts a comma-separated list of types so a single run can
// generate methods for multiple types. The default output file is t_string.go,
// where t is the lower-cased name of the first type listed. It can be overridden
// with the -output flag.
//
// Types can also be declared in tests, in which case type declarations in the
// non-test package or its test variant are preferred over types defined in the
// package with suffix "_test".
// The default output file for type declarations in tests is t_string_test.go with t picked as above.
//
// The -linecomment flag tells stringer to generate the text of any line comment, trimmed
// of leading spaces, instead of the constant name. For instance, if the constants above had a
// Pill prefix, one could write
//
// PillAspirin // Aspirin
//
// to suppress it in the output.
//
// The -trimprefix flag specifies a prefix to remove from the constant names
// when generating the string representations. For instance, -trimprefix=Pill
// would be an alternative way to ensure that PillAspirin.String() == "Aspirin".
package main // import "golang.org/x/tools/cmd/stringer"
import (
"bytes"
"flag"
"fmt"
"go/ast"
"go/constant"
"go/format"
"go/token"
"go/types"
"log"
"os"
"path/filepath"
"sort"
"strings"
"golang.org/x/tools/go/packages"
)
var (
typeNames = flag.String("type", "", "comma-separated list of type names; must be set")
output = flag.String("output", "", "output file name; default srcdir/_string.go")
trimprefix = flag.String("trimprefix", "", "trim the `prefix` from the generated constant names")
linecomment = flag.Bool("linecomment", false, "use line comment text as printed text when present")
buildTags = flag.String("tags", "", "comma-separated list of build tags to apply")
)
// Usage is a replacement usage function for the flags package.
func Usage() {
fmt.Fprintf(os.Stderr, "Usage of stringer:\n")
fmt.Fprintf(os.Stderr, "\tstringer [flags] -type T [directory]\n")
fmt.Fprintf(os.Stderr, "\tstringer [flags] -type T files... # Must be a single package\n")
fmt.Fprintf(os.Stderr, "For more information, see:\n")
fmt.Fprintf(os.Stderr, "\thttps://pkg.go.dev/golang.org/x/tools/cmd/stringer\n")
fmt.Fprintf(os.Stderr, "Flags:\n")
flag.PrintDefaults()
}
func main() {
log.SetFlags(0)
log.SetPrefix("stringer: ")
flag.Usage = Usage
flag.Parse()
if len(*typeNames) == 0 {
flag.Usage()
os.Exit(2)
}
types := strings.Split(*typeNames, ",")
var tags []string
if len(*buildTags) > 0 {
tags = strings.Split(*buildTags, ",")
}
// We accept either one directory or a list of files. Which do we have?
args := flag.Args()
if len(args) == 0 {
// Default: process whole package in current directory.
args = []string{"."}
}
// Parse the package once.
var dir string
// TODO(suzmue): accept other patterns for packages (directories, list of files, import paths, etc).
if len(args) == 1 && isDirectory(args[0]) {
dir = args[0]
} else {
if len(tags) != 0 {
log.Fatal("-tags option applies only to directories, not when files are specified")
}
dir = filepath.Dir(args[0])
}
// For each type, generate code in the first package where the type is declared.
// The order of packages is as follows:
// package x
// package x compiled for tests
// package x_test
//
// Each package pass could result in a separate generated file.
// These files must have the same package and test/not-test nature as the types
// from which they were generated.
//
// Types will be excluded when generated, to avoid repetitions.
pkgs := loadPackages(args, tags, *trimprefix, *linecomment, nil /* logf */)
sort.Slice(pkgs, func(i, j int) bool {
// Put x_test packages last.
iTest := strings.HasSuffix(pkgs[i].name, "_test")
jTest := strings.HasSuffix(pkgs[j].name, "_test")
if iTest != jTest {
return !iTest
}
return len(pkgs[i].files) < len(pkgs[j].files)
})
for _, pkg := range pkgs {
g := Generator{
pkg: pkg,
}
// Print the header and package clause.
g.Printf("// Code generated by \"stringer %s\"; DO NOT EDIT.\n", strings.Join(os.Args[1:], " "))
g.Printf("\n")
g.Printf("package %s", g.pkg.name)
g.Printf("\n")
g.Printf("import \"strconv\"\n") // Used by all methods.
// Run generate for types that can be found. Keep the rest for the remainingTypes iteration.
var foundTypes, remainingTypes []string
for _, typeName := range types {
values := findValues(typeName, pkg)
if len(values) > 0 {
g.generate(typeName, values)
foundTypes = append(foundTypes, typeName)
} else {
remainingTypes = append(remainingTypes, typeName)
}
}
if len(foundTypes) == 0 {
// This package didn't have any of the relevant types, skip writing a file.
continue
}
if len(remainingTypes) > 0 && output != nil && *output != "" {
log.Fatalf("cannot write to single file (-output=%q) when matching types are found in multiple packages", *output)
}
types = remainingTypes
// Format the output.
src := g.format()
// Write to file.
outputName := *output
if outputName == "" {
// Type names will be unique across packages since only the first
// match is picked.
// So there won't be collisions between a package compiled for tests
// and the separate package of tests (package foo_test).
outputName = filepath.Join(dir, baseName(pkg, foundTypes[0]))
}
err := os.WriteFile(outputName, src, 0o644)
if err != nil {
log.Fatalf("writing output: %s", err)
}
}
if len(types) > 0 {
log.Fatalf("no values defined for types: %s", strings.Join(types, ","))
}
}
// baseName that will put the generated code together with pkg.
func baseName(pkg *Package, typename string) string {
suffix := "string.go"
if pkg.hasTestFiles {
suffix = "string_test.go"
}
return fmt.Sprintf("%s_%s", strings.ToLower(typename), suffix)
}
// isDirectory reports whether the named file is a directory.
func isDirectory(name string) bool {
info, err := os.Stat(name)
if err != nil {
log.Fatal(err)
}
return info.IsDir()
}
// Generator holds the state of the analysis. Primarily used to buffer
// the output for format.Source.
type Generator struct {
buf bytes.Buffer // Accumulated output.
pkg *Package // Package we are scanning.
logf func(format string, args ...any) // test logging hook; nil when not testing
}
func (g *Generator) Printf(format string, args ...any) {
fmt.Fprintf(&g.buf, format, args...)
}
// File holds a single parsed file and associated data.
type File struct {
pkg *Package // Package to which this file belongs.
file *ast.File // Parsed AST.
// These fields are reset for each type being generated.
typeName string // Name of the constant type.
values []Value // Accumulator for constant values of that type.
trimPrefix string
lineComment bool
}
type Package struct {
name string
defs map[*ast.Ident]types.Object
files []*File
hasTestFiles bool
}
// loadPackages analyzes the single package constructed from the patterns and tags.
// loadPackages exits if there is an error.
//
// Returns all variants (such as tests) of the package.
//
// logf is a test logging hook. It can be nil when not testing.
func loadPackages(
patterns, tags []string,
trimPrefix string, lineComment bool,
logf func(format string, args ...any),
) []*Package {
cfg := &packages.Config{
Mode: packages.NeedName | packages.NeedTypes | packages.NeedTypesInfo | packages.NeedSyntax | packages.NeedFiles,
// Tests are included, let the caller decide how to fold them in.
Tests: true,
BuildFlags: []string{fmt.Sprintf("-tags=%s", strings.Join(tags, " "))},
Logf: logf,
}
pkgs, err := packages.Load(cfg, patterns...)
if err != nil {
log.Fatal(err)
}
if len(pkgs) == 0 {
log.Fatalf("error: no packages matching %v", strings.Join(patterns, " "))
}
out := make([]*Package, len(pkgs))
for i, pkg := range pkgs {
p := &Package{
name: pkg.Name,
defs: pkg.TypesInfo.Defs,
files: make([]*File, len(pkg.Syntax)),
}
for j, file := range pkg.Syntax {
p.files[j] = &File{
file: file,
pkg: p,
trimPrefix: trimPrefix,
lineComment: lineComment,
}
}
// Keep track of test files, since we might want to generated
// code that ends up in that kind of package.
// Can be replaced once https://go.dev/issue/38445 lands.
for _, f := range pkg.GoFiles {
if strings.HasSuffix(f, "_test.go") {
p.hasTestFiles = true
break
}
}
out[i] = p
}
return out
}
func findValues(typeName string, pkg *Package) []Value {
values := make([]Value, 0, 100)
for _, file := range pkg.files {
// Set the state for this run of the walker.
file.typeName = typeName
file.values = nil
if file.file != nil {
ast.Inspect(file.file, file.genDecl)
values = append(values, file.values...)
}
}
return values
}
// generate produces the String method for the named type.
func (g *Generator) generate(typeName string, values []Value) {
// Generate code that will fail if the constants change value.
g.Printf("func _() {\n")
g.Printf("\t// An \"invalid array index\" compiler error signifies that the constant values have changed.\n")
g.Printf("\t// Re-run the stringer command to generate them again.\n")
g.Printf("\tvar x [1]struct{}\n")
for _, v := range values {
g.Printf("\t_ = x[%s - %s]\n", v.originalName, v.str)
}
g.Printf("}\n")
runs := splitIntoRuns(values)
// The decision of which pattern to use depends on the number of
// runs in the numbers. If there's only one, it's easy. For more than
// one, there's a tradeoff between complexity and size of the data
// and code vs. the simplicity of a map. A map takes more space,
// but so does the code. The decision here (crossover at 10) is
// arbitrary, but considers that for large numbers of runs the cost
// of the linear scan in the switch might become important, and
// rather than use yet another algorithm such as binary search,
// we punt and use a map. In any case, the likelihood of a map
// being necessary for any realistic example other than bitmasks
// is very low. And bitmasks probably deserve their own analysis,
// to be done some other day.
switch {
case len(runs) == 1:
g.buildOneRun(runs, typeName)
case len(runs) <= 10:
g.buildMultipleRuns(runs, typeName)
default:
g.buildMap(runs, typeName)
}
}
// splitIntoRuns breaks the values into runs of contiguous sequences.
// For example, given 1,2,3,5,6,7 it returns {1,2,3},{5,6,7}.
// The input slice is known to be non-empty.
func splitIntoRuns(values []Value) [][]Value {
// We use stable sort so the lexically first name is chosen for equal elements.
sort.Stable(byValue(values))
// Remove duplicates. Stable sort has put the one we want to print first,
// so use that one. The String method won't care about which named constant
// was the argument, so the first name for the given value is the only one to keep.
// We need to do this because identical values would cause the switch or map
// to fail to compile.
j := 1
for i := 1; i < len(values); i++ {
if values[i].value != values[i-1].value {
values[j] = values[i]
j++
}
}
values = values[:j]
runs := make([][]Value, 0, 10)
for len(values) > 0 {
// One contiguous sequence per outer loop.
i := 1
for i < len(values) && values[i].value == values[i-1].value+1 {
i++
}
runs = append(runs, values[:i])
values = values[i:]
}
return runs
}
// format returns the gofmt-ed contents of the Generator's buffer.
func (g *Generator) format() []byte {
src, err := format.Source(g.buf.Bytes())
if err != nil {
// Should never happen, but can arise when developing this code.
// The user can compile the output to see the error.
log.Printf("warning: internal error: invalid Go generated: %s", err)
log.Printf("warning: compile the package to analyze the error")
return g.buf.Bytes()
}
return src
}
// Value represents a declared constant.
type Value struct {
originalName string // The name of the constant.
name string // The name with trimmed prefix.
// The value is stored as a bit pattern alone. The boolean tells us
// whether to interpret it as an int64 or a uint64; the only place
// this matters is when sorting.
// Much of the time the str field is all we need; it is printed
// by Value.String.
value uint64 // Will be converted to int64 when needed.
signed bool // Whether the constant is a signed type.
str string // The string representation given by the "go/constant" package.
}
func (v *Value) String() string {
return v.str
}
// byValue lets us sort the constants into increasing order.
// We take care in the Less method to sort in signed or unsigned order,
// as appropriate.
type byValue []Value
func (b byValue) Len() int { return len(b) }
func (b byValue) Swap(i, j int) { b[i], b[j] = b[j], b[i] }
func (b byValue) Less(i, j int) bool {
if b[i].signed {
return int64(b[i].value) < int64(b[j].value)
}
return b[i].value < b[j].value
}
// genDecl processes one declaration clause.
func (f *File) genDecl(node ast.Node) bool {
decl, ok := node.(*ast.GenDecl)
if !ok || decl.Tok != token.CONST {
// We only care about const declarations.
return true
}
// The name of the type of the constants we are declaring.
// Can change if this is a multi-element declaration.
typ := ""
// Loop over the elements of the declaration. Each element is a ValueSpec:
// a list of names possibly followed by a type, possibly followed by values.
// If the type and value are both missing, we carry down the type (and value,
// but the "go/types" package takes care of that).
for _, spec := range decl.Specs {
vspec := spec.(*ast.ValueSpec) // Guaranteed to succeed as this is CONST.
if vspec.Type == nil && len(vspec.Values) > 0 {
// "X = 1". With no type but a value. If the constant is untyped,
// skip this vspec and reset the remembered type.
typ = ""
// If this is a simple type conversion, remember the type.
// We don't mind if this is actually a call; a qualified call won't
// be matched (that will be SelectorExpr, not Ident), and only unusual
// situations will result in a function call that appears to be
// a type conversion.
ce, ok := vspec.Values[0].(*ast.CallExpr)
if !ok {
continue
}
id, ok := ce.Fun.(*ast.Ident)
if !ok {
continue
}
typ = id.Name
}
if vspec.Type != nil {
// "X T". We have a type. Remember it.
ident, ok := vspec.Type.(*ast.Ident)
if !ok {
continue
}
typ = ident.Name
}
if typ != f.typeName {
// This is not the type we're looking for.
continue
}
// We now have a list of names (from one line of source code) all being
// declared with the desired type.
// Grab their names and actual values and store them in f.values.
for _, name := range vspec.Names {
if name.Name == "_" {
continue
}
// This dance lets the type checker find the values for us. It's a
// bit tricky: look up the object declared by the name, find its
// types.Const, and extract its value.
obj, ok := f.pkg.defs[name]
if !ok {
log.Fatalf("no value for constant %s", name)
}
info := obj.Type().Underlying().(*types.Basic).Info()
if info&types.IsInteger == 0 {
log.Fatalf("can't handle non-integer constant type %s", typ)
}
value := obj.(*types.Const).Val() // Guaranteed to succeed as this is CONST.
if value.Kind() != constant.Int {
log.Fatalf("can't happen: constant is not an integer %s", name)
}
i64, isInt := constant.Int64Val(value)
u64, isUint := constant.Uint64Val(value)
if !isInt && !isUint {
log.Fatalf("internal error: value of %s is not an integer: %s", name, value.String())
}
if !isInt {
u64 = uint64(i64)
}
v := Value{
originalName: name.Name,
value: u64,
signed: info&types.IsUnsigned == 0,
str: value.String(),
}
if c := vspec.Comment; f.lineComment && c != nil && len(c.List) == 1 {
v.name = strings.TrimSpace(c.Text())
} else {
v.name = strings.TrimPrefix(v.originalName, f.trimPrefix)
}
f.values = append(f.values, v)
}
}
return false
}
// Helpers
// usize returns the number of bits of the smallest unsigned integer
// type that will hold n. Used to create the smallest possible slice of
// integers to use as indexes into the concatenated strings.
func usize(n int) int {
switch {
case n < 1<<8:
return 8
case n < 1<<16:
return 16
default:
// 2^32 is enough constants for anyone.
return 32
}
}
// declareIndexAndNameVars declares the index slices and concatenated names
// strings representing the runs of values.
func (g *Generator) declareIndexAndNameVars(runs [][]Value, typeName string) {
var indexes, names []string
for i, run := range runs {
index, name := g.createIndexAndNameDecl(run, typeName, fmt.Sprintf("_%d", i))
if len(run) != 1 {
indexes = append(indexes, index)
}
names = append(names, name)
}
g.Printf("const (\n")
for _, name := range names {
g.Printf("\t%s\n", name)
}
g.Printf(")\n\n")
if len(indexes) > 0 {
g.Printf("var (")
for _, index := range indexes {
g.Printf("\t%s\n", index)
}
g.Printf(")\n\n")
}
}
// declareIndexAndNameVar is the single-run version of declareIndexAndNameVars
func (g *Generator) declareIndexAndNameVar(run []Value, typeName string) {
index, name := g.createIndexAndNameDecl(run, typeName, "")
g.Printf("const %s\n", name)
g.Printf("var %s\n", index)
}
// createIndexAndNameDecl returns the pair of declarations for the run. The caller will add "const" and "var".
func (g *Generator) createIndexAndNameDecl(run []Value, typeName string, suffix string) (string, string) {
b := new(bytes.Buffer)
indexes := make([]int, len(run))
for i := range run {
b.WriteString(run[i].name)
indexes[i] = b.Len()
}
nameConst := fmt.Sprintf("_%s_name%s = %q", typeName, suffix, b.String())
nameLen := b.Len()
b.Reset()
fmt.Fprintf(b, "_%s_index%s = [...]uint%d{0, ", typeName, suffix, usize(nameLen))
for i, v := range indexes {
if i > 0 {
fmt.Fprintf(b, ", ")
}
fmt.Fprintf(b, "%d", v)
}
fmt.Fprintf(b, "}")
return b.String(), nameConst
}
// declareNameVars declares the concatenated names string representing all the values in the runs.
func (g *Generator) declareNameVars(runs [][]Value, typeName string, suffix string) {
g.Printf("const _%s_name%s = \"", typeName, suffix)
for _, run := range runs {
for i := range run {
g.Printf("%s", run[i].name)
}
}
g.Printf("\"\n")
}
// buildOneRun generates the variables and String method for a single run of contiguous values.
func (g *Generator) buildOneRun(runs [][]Value, typeName string) {
values := runs[0]
g.Printf("\n")
g.declareIndexAndNameVar(values, typeName)
g.Printf(stringOneRun, typeName, values[0].String())
}
// Arguments to format are:
//
// [1]: type name
// [2]: lowest defined value for type, as a string
const stringOneRun = `func (i %[1]s) String() string {
idx := int(i) - %[2]s
if i < %[2]s || idx >= len(_%[1]s_index)-1 {
return "%[1]s(" + strconv.FormatInt(int64(i), 10) + ")"
}
return _%[1]s_name[_%[1]s_index[idx] : _%[1]s_index[idx+1]]
}
`
// buildMultipleRuns generates the variables and String method for multiple runs of contiguous values.
// For this pattern, a single Printf format won't do.
func (g *Generator) buildMultipleRuns(runs [][]Value, typeName string) {
g.Printf("\n")
g.declareIndexAndNameVars(runs, typeName)
g.Printf("func (i %s) String() string {\n", typeName)
g.Printf("\tswitch {\n")
for i, values := range runs {
if len(values) == 1 {
g.Printf("\tcase i == %s:\n", &values[0])
g.Printf("\t\treturn _%s_name_%d\n", typeName, i)
continue
}
if values[0].value == 0 && !values[0].signed {
// For an unsigned lower bound of 0, "0 <= i" would be redundant.
g.Printf("\tcase i <= %s:\n", &values[len(values)-1])
} else {
g.Printf("\tcase %s <= i && i <= %s:\n", &values[0], &values[len(values)-1])
}
if values[0].value != 0 {
g.Printf("\t\ti -= %s\n", &values[0])
}
g.Printf("\t\treturn _%s_name_%d[_%s_index_%d[i]:_%s_index_%d[i+1]]\n",
typeName, i, typeName, i, typeName, i)
}
g.Printf("\tdefault:\n")
g.Printf("\t\treturn \"%s(\" + strconv.FormatInt(int64(i), 10) + \")\"\n", typeName)
g.Printf("\t}\n")
g.Printf("}\n")
}
// buildMap handles the case where the space is so sparse a map is a reasonable fallback.
// It's a rare situation but has simple code.
func (g *Generator) buildMap(runs [][]Value, typeName string) {
g.Printf("\n")
g.declareNameVars(runs, typeName, "")
g.Printf("\nvar _%s_map = map[%s]string{\n", typeName, typeName)
n := 0
for _, values := range runs {
for _, value := range values {
g.Printf("\t%s: _%s_name[%d:%d],\n", &value, typeName, n, n+len(value.name))
n += len(value.name)
}
}
g.Printf("}\n\n")
g.Printf(stringMap, typeName)
}
// Argument to format is the type name.
const stringMap = `func (i %[1]s) String() string {
if str, ok := _%[1]s_map[i]; ok {
return str
}
return "%[1]s(" + strconv.FormatInt(int64(i), 10) + ")"
}
`
================================================
FILE: cmd/stringer/testdata/cgo.go
================================================
// Copyright 2014 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Import "C" shouldn't be imported.
package main
/*
#define HELLO 1
*/
import "C"
import "fmt"
type Cgo uint32
const (
// MustScanSubDirs indicates that events were coalesced hierarchically.
MustScanSubDirs Cgo = 1 << iota
)
func main() {
_ = C.HELLO
ck(MustScanSubDirs, "MustScanSubDirs")
}
func ck(day Cgo, str string) {
if fmt.Sprint(day) != str {
panic("cgo.go: " + str)
}
}
================================================
FILE: cmd/stringer/testdata/conv.go
================================================
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Check that constants defined as a conversion are accepted.
package main
import "fmt"
type Other int // Imagine this is in another package.
const (
alpha Other = iota
beta
gamma
delta
)
type Conv int
const (
Alpha = Conv(alpha)
Beta = Conv(beta)
Gamma = Conv(gamma)
Delta = Conv(delta)
)
func main() {
ck(Alpha, "Alpha")
ck(Beta, "Beta")
ck(Gamma, "Gamma")
ck(Delta, "Delta")
ck(42, "Conv(42)")
}
func ck(c Conv, str string) {
if fmt.Sprint(c) != str {
panic("conv.go: " + str)
}
}
================================================
FILE: cmd/stringer/testdata/conv2.go
================================================
// Copyright 2021 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// This is a version of ../conv.go with type params
// Check that constants defined as a conversion are accepted.
package main
import "fmt"
// For now, a lone type parameter is not permitted as RHS in a type declaration (issue #45639).
// type Other[T interface{ ~int | ~uint }] T // Imagine this is in another package.
type Other int
const (
// alpha Other[int] = iota
alpha Other = iota
beta
gamma
delta
)
// type Conv2 Other[int]
type Conv2 Other
const (
Alpha = Conv2(alpha)
Beta = Conv2(beta)
Gamma = Conv2(gamma)
Delta = Conv2(delta)
)
func main() {
ck(Alpha, "Alpha")
ck(Beta, "Beta")
ck(Gamma, "Gamma")
ck(Delta, "Delta")
ck(42, "Conv2(42)")
}
func ck(c Conv2, str string) {
if fmt.Sprint(c) != str {
panic("conv2.go: " + str)
}
}
================================================
FILE: cmd/stringer/testdata/day.go
================================================
// Copyright 2014 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Simple test: enumeration of type int starting at 0.
package main
import "fmt"
type Day int
const (
Monday Day = iota
Tuesday
Wednesday
Thursday
Friday
Saturday
Sunday
)
func main() {
ck(Monday, "Monday")
ck(Tuesday, "Tuesday")
ck(Wednesday, "Wednesday")
ck(Thursday, "Thursday")
ck(Friday, "Friday")
ck(Saturday, "Saturday")
ck(Sunday, "Sunday")
ck(-127, "Day(-127)")
ck(127, "Day(127)")
}
func ck(day Day, str string) {
if fmt.Sprint(day) != str {
panic("day.go: " + str)
}
}
================================================
FILE: cmd/stringer/testdata/gap.go
================================================
// Copyright 2014 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Gaps and an offset.
package main
import "fmt"
type Gap int
const (
Two Gap = 2
Three Gap = 3
Five Gap = 5
Six Gap = 6
Seven Gap = 7
Eight Gap = 8
Nine Gap = 9
Eleven Gap = 11
)
func main() {
ck(0, "Gap(0)")
ck(1, "Gap(1)")
ck(Two, "Two")
ck(Three, "Three")
ck(4, "Gap(4)")
ck(Five, "Five")
ck(Six, "Six")
ck(Seven, "Seven")
ck(Eight, "Eight")
ck(Nine, "Nine")
ck(10, "Gap(10)")
ck(Eleven, "Eleven")
ck(12, "Gap(12)")
}
func ck(gap Gap, str string) {
if fmt.Sprint(gap) != str {
panic("gap.go: " + str)
}
}
================================================
FILE: cmd/stringer/testdata/int8overflow.go
================================================
// Copyright 2025 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Check that int8 bounds checking doesn't cause panics or compilation errors.
package main
import (
"fmt"
"strings"
)
type Int8overflow int8
const (
I_128 Int8overflow = -128
I_127 Int8overflow = -127
I_126 Int8overflow = -126
I_125 Int8overflow = -125
I_124 Int8overflow = -124
I_123 Int8overflow = -123
I_122 Int8overflow = -122
I_121 Int8overflow = -121
I_120 Int8overflow = -120
I_119 Int8overflow = -119
I_118 Int8overflow = -118
I_117 Int8overflow = -117
I_116 Int8overflow = -116
I_115 Int8overflow = -115
I_114 Int8overflow = -114
I_113 Int8overflow = -113
I_112 Int8overflow = -112
I_111 Int8overflow = -111
I_110 Int8overflow = -110
I_109 Int8overflow = -109
I_108 Int8overflow = -108
I_107 Int8overflow = -107
I_106 Int8overflow = -106
I_105 Int8overflow = -105
I_104 Int8overflow = -104
I_103 Int8overflow = -103
I_102 Int8overflow = -102
I_101 Int8overflow = -101
I_100 Int8overflow = -100
I_99 Int8overflow = -99
I_98 Int8overflow = -98
I_97 Int8overflow = -97
I_96 Int8overflow = -96
I_95 Int8overflow = -95
I_94 Int8overflow = -94
I_93 Int8overflow = -93
I_92 Int8overflow = -92
I_91 Int8overflow = -91
I_90 Int8overflow = -90
I_89 Int8overflow = -89
I_88 Int8overflow = -88
I_87 Int8overflow = -87
I_86 Int8overflow = -86
I_85 Int8overflow = -85
I_84 Int8overflow = -84
I_83 Int8overflow = -83
I_82 Int8overflow = -82
I_81 Int8overflow = -81
I_80 Int8overflow = -80
I_79 Int8overflow = -79
I_78 Int8overflow = -78
I_77 Int8overflow = -77
I_76 Int8overflow = -76
I_75 Int8overflow = -75
I_74 Int8overflow = -74
I_73 Int8overflow = -73
I_72 Int8overflow = -72
I_71 Int8overflow = -71
I_70 Int8overflow = -70
I_69 Int8overflow = -69
I_68 Int8overflow = -68
I_67 Int8overflow = -67
I_66 Int8overflow = -66
I_65 Int8overflow = -65
I_64 Int8overflow = -64
I_63 Int8overflow = -63
I_62 Int8overflow = -62
I_61 Int8overflow = -61
I_60 Int8overflow = -60
I_59 Int8overflow = -59
I_58 Int8overflow = -58
I_57 Int8overflow = -57
I_56 Int8overflow = -56
I_55 Int8overflow = -55
I_54 Int8overflow = -54
I_53 Int8overflow = -53
I_52 Int8overflow = -52
I_51 Int8overflow = -51
I_50 Int8overflow = -50
I_49 Int8overflow = -49
I_48 Int8overflow = -48
I_47 Int8overflow = -47
I_46 Int8overflow = -46
I_45 Int8overflow = -45
I_44 Int8overflow = -44
I_43 Int8overflow = -43
I_42 Int8overflow = -42
I_41 Int8overflow = -41
I_40 Int8overflow = -40
I_39 Int8overflow = -39
I_38 Int8overflow = -38
I_37 Int8overflow = -37
I_36 Int8overflow = -36
I_35 Int8overflow = -35
I_34 Int8overflow = -34
I_33 Int8overflow = -33
I_32 Int8overflow = -32
I_31 Int8overflow = -31
I_30 Int8overflow = -30
I_29 Int8overflow = -29
I_28 Int8overflow = -28
I_27 Int8overflow = -27
I_26 Int8overflow = -26
I_25 Int8overflow = -25
I_24 Int8overflow = -24
I_23 Int8overflow = -23
I_22 Int8overflow = -22
I_21 Int8overflow = -21
I_20 Int8overflow = -20
I_19 Int8overflow = -19
I_18 Int8overflow = -18
I_17 Int8overflow = -17
I_16 Int8overflow = -16
I_15 Int8overflow = -15
I_14 Int8overflow = -14
I_13 Int8overflow = -13
I_12 Int8overflow = -12
I_11 Int8overflow = -11
I_10 Int8overflow = -10
I_9 Int8overflow = -9
I_8 Int8overflow = -8
I_7 Int8overflow = -7
I_6 Int8overflow = -6
I_5 Int8overflow = -5
I_4 Int8overflow = -4
I_3 Int8overflow = -3
I_2 Int8overflow = -2
I_1 Int8overflow = -1
I0 Int8overflow = 0
I1 Int8overflow = 1
I2 Int8overflow = 2
I3 Int8overflow = 3
I4 Int8overflow = 4
I5 Int8overflow = 5
I6 Int8overflow = 6
I7 Int8overflow = 7
I8 Int8overflow = 8
I9 Int8overflow = 9
I10 Int8overflow = 10
I11 Int8overflow = 11
I12 Int8overflow = 12
I13 Int8overflow = 13
I14 Int8overflow = 14
I15 Int8overflow = 15
I16 Int8overflow = 16
I17 Int8overflow = 17
I18 Int8overflow = 18
I19 Int8overflow = 19
I20 Int8overflow = 20
I21 Int8overflow = 21
I22 Int8overflow = 22
I23 Int8overflow = 23
I24 Int8overflow = 24
I25 Int8overflow = 25
I26 Int8overflow = 26
I27 Int8overflow = 27
I28 Int8overflow = 28
I29 Int8overflow = 29
I30 Int8overflow = 30
I31 Int8overflow = 31
I32 Int8overflow = 32
I33 Int8overflow = 33
I34 Int8overflow = 34
I35 Int8overflow = 35
I36 Int8overflow = 36
I37 Int8overflow = 37
I38 Int8overflow = 38
I39 Int8overflow = 39
I40 Int8overflow = 40
I41 Int8overflow = 41
I42 Int8overflow = 42
I43 Int8overflow = 43
I44 Int8overflow = 44
I45 Int8overflow = 45
I46 Int8overflow = 46
I47 Int8overflow = 47
I48 Int8overflow = 48
I49 Int8overflow = 49
I50 Int8overflow = 50
I51 Int8overflow = 51
I52 Int8overflow = 52
I53 Int8overflow = 53
I54 Int8overflow = 54
I55 Int8overflow = 55
I56 Int8overflow = 56
I57 Int8overflow = 57
I58 Int8overflow = 58
I59 Int8overflow = 59
I60 Int8overflow = 60
I61 Int8overflow = 61
I62 Int8overflow = 62
I63 Int8overflow = 63
I64 Int8overflow = 64
I65 Int8overflow = 65
I66 Int8overflow = 66
I67 Int8overflow = 67
I68 Int8overflow = 68
I69 Int8overflow = 69
I70 Int8overflow = 70
I71 Int8overflow = 71
I72 Int8overflow = 72
I73 Int8overflow = 73
I74 Int8overflow = 74
I75 Int8overflow = 75
I76 Int8overflow = 76
I77 Int8overflow = 77
I78 Int8overflow = 78
I79 Int8overflow = 79
I80 Int8overflow = 80
I81 Int8overflow = 81
I82 Int8overflow = 82
I83 Int8overflow = 83
I84 Int8overflow = 84
I85 Int8overflow = 85
I86 Int8overflow = 86
I87 Int8overflow = 87
I88 Int8overflow = 88
I89 Int8overflow = 89
I90 Int8overflow = 90
I91 Int8overflow = 91
I92 Int8overflow = 92
I93 Int8overflow = 93
I94 Int8overflow = 94
I95 Int8overflow = 95
I96 Int8overflow = 96
I97 Int8overflow = 97
I98 Int8overflow = 98
I99 Int8overflow = 99
I100 Int8overflow = 100
I101 Int8overflow = 101
I102 Int8overflow = 102
I103 Int8overflow = 103
I104 Int8overflow = 104
I105 Int8overflow = 105
I106 Int8overflow = 106
I107 Int8overflow = 107
I108 Int8overflow = 108
I109 Int8overflow = 109
I110 Int8overflow = 110
I111 Int8overflow = 111
I112 Int8overflow = 112
I113 Int8overflow = 113
I114 Int8overflow = 114
I115 Int8overflow = 115
I116 Int8overflow = 116
I117 Int8overflow = 117
I118 Int8overflow = 118
I119 Int8overflow = 119
I120 Int8overflow = 120
I121 Int8overflow = 121
I122 Int8overflow = 122
I123 Int8overflow = 123
I124 Int8overflow = 124
I125 Int8overflow = 125
I126 Int8overflow = 126
I127 Int8overflow = 127
)
func main() {
testValues := []Int8overflow{
I_128,
I_127,
I_1,
I0,
I1,
I126,
I127,
}
for _, val := range testValues {
want := strings.ReplaceAll(fmt.Sprintf("I%d", int(val)), "-", "_")
result := fmt.Sprint(val)
if result != want {
panic(fmt.Sprintf("int8overflow.go: got %s, want %s for value %d", result, want, int8(val)))
}
}
}
================================================
FILE: cmd/stringer/testdata/num.go
================================================
// Copyright 2014 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Signed integers spanning zero.
package main
import "fmt"
type Num int
const (
m_2 Num = -2 + iota
m_1
m0
m1
m2
)
func main() {
ck(-3, "Num(-3)")
ck(m_2, "m_2")
ck(m_1, "m_1")
ck(m0, "m0")
ck(m1, "m1")
ck(m2, "m2")
ck(3, "Num(3)")
}
func ck(num Num, str string) {
if fmt.Sprint(num) != str {
panic("num.go: " + str)
}
}
================================================
FILE: cmd/stringer/testdata/number.go
================================================
// Copyright 2014 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Enumeration with an offset.
// Also includes a duplicate.
package main
import "fmt"
type Number int
const (
_ Number = iota
One
Two
Three
AnotherOne = One // Duplicate; note that AnotherOne doesn't appear below.
)
func main() {
ck(One, "One")
ck(Two, "Two")
ck(Three, "Three")
ck(AnotherOne, "One")
ck(127, "Number(127)")
}
func ck(num Number, str string) {
if fmt.Sprint(num) != str {
panic("number.go: " + str)
}
}
================================================
FILE: cmd/stringer/testdata/prime.go
================================================
// Copyright 2014 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Enough gaps to trigger a map implementation of the method.
// Also includes a duplicate to test that it doesn't cause problems
package main
import "fmt"
type Prime int
const (
p2 Prime = 2
p3 Prime = 3
p5 Prime = 5
p7 Prime = 7
p77 Prime = 7 // Duplicate; note that p77 doesn't appear below.
p11 Prime = 11
p13 Prime = 13
p17 Prime = 17
p19 Prime = 19
p23 Prime = 23
p29 Prime = 29
p37 Prime = 31
p41 Prime = 41
p43 Prime = 43
)
func main() {
ck(0, "Prime(0)")
ck(1, "Prime(1)")
ck(p2, "p2")
ck(p3, "p3")
ck(4, "Prime(4)")
ck(p5, "p5")
ck(p7, "p7")
ck(p77, "p7")
ck(p11, "p11")
ck(p13, "p13")
ck(p17, "p17")
ck(p19, "p19")
ck(p23, "p23")
ck(p29, "p29")
ck(p37, "p37")
ck(p41, "p41")
ck(p43, "p43")
ck(44, "Prime(44)")
}
func ck(prime Prime, str string) {
if fmt.Sprint(prime) != str {
panic("prime.go: " + str)
}
}
================================================
FILE: cmd/stringer/testdata/prime2.go
================================================
// Copyright 2021 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// This is a version of ../prime.go with type params
// Enough gaps to trigger a map implementation of the method.
// Also includes a duplicate to test that it doesn't cause problems
package main
import "fmt"
// For now, a lone type parameter is not permitted as RHS in a type declaration (issue #45639).
// type Likeint[T interface{ ~int | ~uint8 }] T
type Likeint int
// type Prime2 Likeint[int]
type Prime2 Likeint
const (
p2 Prime2 = 2
p3 Prime2 = 3
p5 Prime2 = 5
p7 Prime2 = 7
p77 Prime2 = 7 // Duplicate; note that p77 doesn't appear below.
p11 Prime2 = 11
p13 Prime2 = 13
p17 Prime2 = 17
p19 Prime2 = 19
p23 Prime2 = 23
p29 Prime2 = 29
p37 Prime2 = 31
p41 Prime2 = 41
p43 Prime2 = 43
)
func main() {
ck(0, "Prime2(0)")
ck(1, "Prime2(1)")
ck(p2, "p2")
ck(p3, "p3")
ck(4, "Prime2(4)")
ck(p5, "p5")
ck(p7, "p7")
ck(p77, "p7")
ck(p11, "p11")
ck(p13, "p13")
ck(p17, "p17")
ck(p19, "p19")
ck(p23, "p23")
ck(p29, "p29")
ck(p37, "p37")
ck(p41, "p41")
ck(p43, "p43")
ck(44, "Prime2(44)")
}
func ck(prime Prime2, str string) {
if fmt.Sprint(prime) != str {
panic("prime2.go: " + str)
}
}
================================================
FILE: cmd/stringer/testdata/tag_main.go
================================================
// No build tag in this file.
package main
type Const int
const (
A Const = iota
B
C
)
================================================
FILE: cmd/stringer/testdata/tag_tag.go
================================================
// This file has a build tag "tag"
// +build tag
package main
const TagProtected Const = C + 1
================================================
FILE: cmd/stringer/testdata/unum.go
================================================
// Copyright 2014 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Unsigned integers spanning zero.
package main
import "fmt"
type Unum uint8
const (
m_2 Unum = iota + 253
m_1
)
const (
m0 Unum = iota
m1
m2
)
func main() {
ck(^Unum(0)-3, "Unum(252)")
ck(m_2, "m_2")
ck(m_1, "m_1")
ck(m0, "m0")
ck(m1, "m1")
ck(m2, "m2")
ck(3, "Unum(3)")
}
func ck(unum Unum, str string) {
if fmt.Sprint(unum) != str {
panic("unum.go: " + str)
}
}
================================================
FILE: cmd/stringer/testdata/unum2.go
================================================
// Copyright 2014 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Unsigned integers - check maximum size
package main
import "fmt"
type Unum2 uint8
const (
Zero Unum2 = iota
One
Two
)
func main() {
ck(Zero, "Zero")
ck(One, "One")
ck(Two, "Two")
ck(3, "Unum2(3)")
ck(255, "Unum2(255)")
}
func ck(unum Unum2, str string) {
if fmt.Sprint(unum) != str {
panic("unum.go: " + str)
}
}
================================================
FILE: cmd/stringer/testdata/vary_day.go
================================================
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// This file is the same as day.go except the constants have different values.
package main
import "fmt"
type Day int
const (
Sunday Day = iota
Monday
Tuesday
Wednesday
Thursday
Friday
Saturday
)
func main() {
ck(Monday, "Monday")
ck(Tuesday, "Tuesday")
ck(Wednesday, "Wednesday")
ck(Thursday, "Thursday")
ck(Friday, "Friday")
ck(Saturday, "Saturday")
ck(Sunday, "Sunday")
ck(-127, "Day(-127)")
ck(127, "Day(127)")
}
func ck(day Day, str string) {
if fmt.Sprint(day) != str {
panic("day.go: " + str)
}
}
================================================
FILE: cmd/stringer/util_test.go
================================================
// Copyright 2014 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// This file contains tests for some of the internal functions.
package main
import (
"fmt"
"testing"
)
// Helpers to save typing in the test cases.
type u []uint64
type uu [][]uint64
type SplitTest struct {
input u
output uu
signed bool
}
var (
m2 = uint64(2)
m1 = uint64(1)
m0 = uint64(0)
m_1 = ^uint64(0) // -1 when signed.
m_2 = ^uint64(0) - 1 // -2 when signed.
)
var splitTests = []SplitTest{
// No need for a test for the empty case; that's picked off before splitIntoRuns.
// Single value.
{u{1}, uu{u{1}}, false},
// Out of order.
{u{3, 2, 1}, uu{u{1, 2, 3}}, true},
// Out of order.
{u{3, 2, 1}, uu{u{1, 2, 3}}, false},
// A gap at the beginning.
{u{1, 33, 32, 31}, uu{u{1}, u{31, 32, 33}}, true},
// A gap in the middle, in mixed order.
{u{33, 7, 32, 31, 9, 8}, uu{u{7, 8, 9}, u{31, 32, 33}}, true},
// Gaps throughout
{u{33, 44, 1, 32, 45, 31}, uu{u{1}, u{31, 32, 33}, u{44, 45}}, true},
// Unsigned values spanning 0.
{u{m1, m0, m_1, m2, m_2}, uu{u{m0, m1, m2}, u{m_2, m_1}}, false},
// Signed values spanning 0
{u{m1, m0, m_1, m2, m_2}, uu{u{m_2, m_1, m0, m1, m2}}, true},
}
func TestSplitIntoRuns(t *testing.T) {
Outer:
for n, test := range splitTests {
values := make([]Value, len(test.input))
for i, v := range test.input {
values[i] = Value{"", "", v, test.signed, fmt.Sprint(v)}
}
runs := splitIntoRuns(values)
if len(runs) != len(test.output) {
t.Errorf("#%d: %v: got %d runs; expected %d", n, test.input, len(runs), len(test.output))
continue
}
for i, run := range runs {
if len(run) != len(test.output[i]) {
t.Errorf("#%d: got %v; expected %v", n, runs, test.output)
continue Outer
}
for j, v := range run {
if v.value != test.output[i][j] {
t.Errorf("#%d: got %v; expected %v", n, runs, test.output)
continue Outer
}
}
}
}
}
================================================
FILE: cmd/toolstash/buildall
================================================
#!/bin/bash
# Usage: buildall [-e] [-nocmp] [-work]
#
# Builds everything (std) for every GOOS/GOARCH combination but installs nothing.
#
# By default, runs the builds with -toolexec 'toolstash -cmp', to test that the
# toolchain is producing bit identical output to a previous known good toolchain.
#
# Options:
# -e: stop at first failure
# -nocmp: turn off toolstash -cmp; just check that ordinary builds succeed
# -work: pass -work to go command
sete=false
if [ "$1" = "-e" ]; then
sete=true
shift
fi
cmp=true
if [ "$1" = "-nocmp" ]; then
cmp=false
shift
fi
work=""
if [ "$1" = "-work" ]; then
work="-work"
shift
fi
cd $(go env GOROOT)/src
go install cmd/compile cmd/link cmd/asm || exit 1
pattern="$1"
if [ "$pattern" = "" ]; then
pattern=.
fi
targets="$(go tool dist list; echo linux/386/softfloat)"
targets="$(echo "$targets" | tr '/' '-' | sort | grep -E "$pattern" | grep -E -v 'android-arm|darwin-arm')"
# put linux first in the target list to get all the architectures up front.
targets="$(echo "$targets" | grep -E 'linux') $(echo "$targets" | grep -E -v 'linux')"
if [ "$sete" = true ]; then
set -e
fi
for target in $targets
do
echo $target
export GOOS=$(echo $target | sed 's/-.*//')
export GOARCH=$(echo $target | sed 's/.*-//')
unset GO386
if [ "$GOARCH" = "softfloat" ]; then
export GOARCH=386
export GO386=softfloat
fi
if $cmp; then
if [ "$GOOS" = "android" ]; then
go build $work -a -toolexec 'toolstash -cmp' std
else
go build $work -a -toolexec 'toolstash -cmp' std cmd
fi
else
go build $work -a std
fi
done
================================================
FILE: cmd/toolstash/cmp.go
================================================
// Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package main
import (
"bufio"
"bytes"
"fmt"
"io"
"log"
"os"
"regexp"
"strconv"
"strings"
)
var (
hexDumpRE = regexp.MustCompile(`^\t(0x[0-9a-f]{4,})(( ([0-9a-f]{2}| )){16}) [ -\x7F]{1,16}\n`)
listingRE = regexp.MustCompile(`^\t(0x[0-9a-f]{4,}) ([0-9]{4,}) \(.*:[0-9]+\)\t`)
)
// okdiffs lists regular expressions for lines to consider minor mismatches.
// If one of these regexps matches both of a pair of unequal lines, the mismatch
// is reported but not treated as the one worth looking for.
// For example, differences in the TEXT line are typically frame size
// changes due to optimization decisions made in the body of the function.
// Better to keep looking for the actual difference.
// Similarly, forward jumps might have different offsets due to a
// change in instruction encoding later on.
// Better to find that change.
var okdiffs = []*regexp.Regexp{
regexp.MustCompile(`\) TEXT[ ].*,\$`),
regexp.MustCompile(`\) WORD[ ].*,\$`),
regexp.MustCompile(`\) (B|BR|JMP) `),
regexp.MustCompile(`\) FUNCDATA `),
regexp.MustCompile(`\\(z|x00)`),
regexp.MustCompile(`\$\([0-9]\.[0-9]+e[+\-][0-9]+\)`),
regexp.MustCompile(`size=.*value=.*args=.*locals=`),
}
func compareLogs(outfile string) string {
f1, err := os.Open(outfile + ".log")
if err != nil {
log.Fatal(err)
}
defer f1.Close()
f2, err := os.Open(outfile + ".stash.log")
if err != nil {
log.Fatal(err)
}
defer f2.Close()
b1 := bufio.NewReader(f1)
b2 := bufio.NewReader(f2)
offset := int64(0)
textOffset := offset
textLineno := 0
lineno := 0
var line1, line2 string
var prefix bytes.Buffer
Reading:
for {
var err1, err2 error
line1, err1 = b1.ReadString('\n')
line2, err2 = b2.ReadString('\n')
if strings.Contains(line1, ")\tTEXT\t") {
textOffset = offset
textLineno = lineno
}
offset += int64(len(line1))
lineno++
if err1 == io.EOF && err2 == io.EOF {
return "no differences in debugging output"
}
if lineno == 1 || line1 == line2 && err1 == nil && err2 == nil {
continue
}
// Lines are inconsistent. Worth stopping?
for _, re := range okdiffs {
if re.MatchString(line1) && re.MatchString(line2) {
fmt.Fprintf(&prefix, "inconsistent log line:\n%s:%d:\n\t%s\n%s:%d:\n\t%s\n\n",
f1.Name(), lineno, strings.TrimSuffix(line1, "\n"),
f2.Name(), lineno, strings.TrimSuffix(line2, "\n"))
continue Reading
}
}
if err1 != nil {
line1 = err1.Error()
}
if err2 != nil {
line2 = err2.Error()
}
break
}
msg := fmt.Sprintf("inconsistent log line:\n%s:%d:\n\t%s\n%s:%d:\n\t%s",
f1.Name(), lineno, strings.TrimSuffix(line1, "\n"),
f2.Name(), lineno, strings.TrimSuffix(line2, "\n"))
if m := hexDumpRE.FindStringSubmatch(line1); m != nil {
target, err := strconv.ParseUint(m[1], 0, 64)
if err != nil {
goto Skip
}
m2 := hexDumpRE.FindStringSubmatch(line2)
if m2 == nil {
goto Skip
}
fields1 := strings.Fields(m[2])
fields2 := strings.Fields(m2[2])
i := 0
for i < len(fields1) && i < len(fields2) && fields1[i] == fields2[i] {
i++
}
target += uint64(i)
f1.Seek(textOffset, 0)
b1 = bufio.NewReader(f1)
last := ""
lineno := textLineno
limitAddr := uint64(0)
lastAddr := uint64(0)
for {
line1, err1 := b1.ReadString('\n')
if err1 != nil {
break
}
lineno++
if m := listingRE.FindStringSubmatch(line1); m != nil {
addr, _ := strconv.ParseUint(m[1], 0, 64)
if addr > target {
limitAddr = addr
break
}
last = line1
lastAddr = addr
} else if hexDumpRE.FindStringSubmatch(line1) != nil {
break
}
}
if last != "" {
msg = fmt.Sprintf("assembly instruction at %#04x-%#04x:\n%s:%d\n\t%s\n\n%s",
lastAddr, limitAddr, f1.Name(), lineno-1, strings.TrimSuffix(last, "\n"), msg)
}
}
Skip:
return prefix.String() + msg
}
================================================
FILE: cmd/toolstash/main.go
================================================
// Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Toolstash provides a way to save, run, and restore a known good copy of the Go toolchain
// and to compare the object files generated by two toolchains.
//
// Usage:
//
// toolstash [-n] [-v] save [tool...]
// toolstash [-n] [-v] restore [tool...]
// toolstash [-n] [-v] [-t] go run x.go
// toolstash [-n] [-v] [-t] [-cmp] compile x.go
//
// The toolstash command manages a “stashed” copy of the Go toolchain
// kept in $GOROOT/pkg/toolstash. In this case, the toolchain means the
// tools available with the 'go tool' command as well as the go, godoc, and gofmt
// binaries.
//
// The command “toolstash save”, typically run when the toolchain is known to be working,
// copies the toolchain from its installed location to the toolstash directory.
// Its inverse, “toolchain restore”, typically run when the toolchain is known to be broken,
// copies the toolchain from the toolstash directory back to the installed locations.
// If additional arguments are given, the save or restore applies only to the named tools.
// Otherwise, it applies to all tools.
//
// Otherwise, toolstash's arguments should be a command line beginning with the
// name of a toolchain binary, which may be a short name like compile or a complete path
// to an installed binary. Toolstash runs the command line using the stashed
// copy of the binary instead of the installed one.
//
// The -n flag causes toolstash to print the commands that would be executed
// but not execute them. The combination -n -cmp shows the two commands
// that would be compared and then exits successfully. A real -cmp run might
// run additional commands for diagnosis of an output mismatch.
//
// The -v flag causes toolstash to print the commands being executed.
//
// The -t flag causes toolstash to print the time elapsed during while the
// command ran.
//
// # Comparing
//
// The -cmp flag causes toolstash to run both the installed and the stashed
// copy of an assembler or compiler and check that they produce identical
// object files. If not, toolstash reports the mismatch and exits with a failure status.
// As part of reporting the mismatch, toolstash reinvokes the command with
// the -S=2 flag and identifies the first divergence in the assembly output.
// If the command is a Go compiler, toolstash also determines whether the
// difference is triggered by optimization passes.
// On failure, toolstash leaves additional information in files named
// similarly to the default output file. If the compilation would normally
// produce a file x.6, the output from the stashed tool is left in x.6.stash
// and the debugging traces are left in x.6.log and x.6.stash.log.
//
// The -cmp flag is a no-op when the command line is not invoking an
// assembler or compiler.
//
// For example, when working on code cleanup that should not affect
// compiler output, toolstash can be used to compare the old and new
// compiler output:
//
// toolstash save
//
// go tool dist install cmd/compile # install compiler only
// toolstash -cmp compile x.go
//
// # Go Command Integration
//
// The go command accepts a -toolexec flag that specifies a program
// to use to run the build tools.
//
// To build with the stashed tools:
//
// go build -toolexec toolstash x.go
//
// To build with the stashed go command and the stashed tools:
//
// toolstash go build -toolexec toolstash x.go
//
// To verify that code cleanup in the compilers does not make any
// changes to the objects being generated for the entire tree:
//
// # Build working tree and save tools.
// ./make.bash
// toolstash save
//
//
//
// # Install new tools, but do not rebuild the rest of tree,
// # since the compilers might generate buggy code.
// go tool dist install cmd/compile
//
// # Check that new tools behave identically to saved tools.
// go build -toolexec 'toolstash -cmp' -a std
//
// # If not, restore, in order to keep working on Go code.
// toolstash restore
//
// # Version Skew
//
// The Go tools write the current Go version to object files, and (outside
// release branches) that version includes the hash and time stamp
// of the most recent Git commit. Functionally equivalent
// compilers built at different Git versions may produce object files that
// differ only in the recorded version. Toolstash ignores version mismatches
// when comparing object files, but the standard tools will refuse to compile
// or link together packages with different object versions.
//
// For the full build in the final example above to work, both the stashed
// and the installed tools must use the same version string.
// One way to ensure this is not to commit any of the changes being
// tested, so that the Git HEAD hash is the same for both builds.
// A more robust way to force the tools to have the same version string
// is to write a $GOROOT/VERSION file, which overrides the Git-based version
// computation:
//
// echo devel >$GOROOT/VERSION
//
// The version can be arbitrary text, but to pass all.bash's API check, it must
// contain the substring “devel”. The VERSION file must be created before
// building either version of the toolchain.
package main // import "golang.org/x/tools/cmd/toolstash"
import (
"bufio"
"flag"
"fmt"
"io"
"log"
"os"
"os/exec"
"path/filepath"
"runtime"
"strings"
"time"
)
var usageMessage = `usage: toolstash [-n] [-v] [-cmp] command line
Examples:
toolstash save
toolstash restore
toolstash go run x.go
toolstash compile x.go
toolstash -cmp compile x.go
For details, godoc golang.org/x/tools/cmd/toolstash
`
func usage() {
fmt.Fprint(os.Stderr, usageMessage)
os.Exit(2)
}
var (
goCmd = flag.String("go", "go", "path to \"go\" command")
norun = flag.Bool("n", false, "print but do not run commands")
verbose = flag.Bool("v", false, "print commands being run")
cmp = flag.Bool("cmp", false, "compare tool object files")
timing = flag.Bool("t", false, "print time commands take")
)
var (
cmd []string
tool string // name of tool: "go", "compile", etc
toolStash string // path to stashed tool
goroot string
toolDir string
stashDir string
binDir string
)
func canCmp(name string, args []string) bool {
switch name {
case "asm", "compile", "link":
if len(args) == 1 && (args[0] == "-V" || strings.HasPrefix(args[0], "-V=")) {
// cmd/go uses "compile -V=full" to query the tool's build ID.
return false
}
return true
}
return len(name) == 2 && '0' <= name[0] && name[0] <= '9' && (name[1] == 'a' || name[1] == 'g' || name[1] == 'l')
}
var binTools = []string{"go", "godoc", "gofmt"}
func isBinTool(name string) bool {
return strings.HasPrefix(name, "go")
}
func main() {
log.SetFlags(0)
log.SetPrefix("toolstash: ")
flag.Usage = usage
flag.Parse()
cmd = flag.Args()
if len(cmd) < 1 {
usage()
}
s, err := exec.Command(*goCmd, "env", "GOROOT").CombinedOutput()
if err != nil {
log.Fatalf("%s env GOROOT: %v", *goCmd, err)
}
goroot = strings.TrimSpace(string(s))
toolDir = filepath.Join(goroot, fmt.Sprintf("pkg/tool/%s_%s", runtime.GOOS, runtime.GOARCH))
stashDir = filepath.Join(goroot, "pkg/toolstash")
binDir = os.Getenv("GOBIN")
if binDir == "" {
binDir = filepath.Join(goroot, "bin")
}
switch cmd[0] {
case "save":
save()
return
case "restore":
restore()
return
}
tool = exeName(cmd[0])
if i := strings.LastIndexAny(tool, `/\`); i >= 0 {
tool = tool[i+1:]
}
if !strings.HasPrefix(tool, "a.out") {
toolStash = filepath.Join(stashDir, tool)
if _, err := os.Stat(toolStash); err != nil {
log.Print(err)
os.Exit(2)
}
if *cmp && canCmp(tool, cmd[1:]) {
compareTool()
return
}
cmd[0] = toolStash
}
if *norun {
fmt.Printf("%s\n", strings.Join(cmd, " "))
return
}
if *verbose {
log.Print(strings.Join(cmd, " "))
}
xcmd := exec.Command(cmd[0], cmd[1:]...)
xcmd.Stdin = os.Stdin
xcmd.Stdout = os.Stdout
xcmd.Stderr = os.Stderr
err = xcmd.Run()
if err != nil {
log.Fatal(err)
}
os.Exit(0)
}
func compareTool() {
if !strings.Contains(cmd[0], "/") && !strings.Contains(cmd[0], `\`) {
cmd[0] = filepath.Join(toolDir, tool)
}
outfile, ok := cmpRun(false, cmd)
if ok {
os.Remove(outfile + ".stash")
return
}
extra := "-S=2"
switch {
default:
log.Fatalf("unknown tool %s", tool)
case tool == "compile" || strings.HasSuffix(tool, "g"): // compiler
useDashN := true
dashcIndex := -1
for i, s := range cmd {
if s == "-+" {
// Compiling runtime. Don't use -N.
useDashN = false
}
if strings.HasPrefix(s, "-c=") {
dashcIndex = i
}
}
cmdN := injectflags(cmd, nil, useDashN)
_, ok := cmpRun(false, cmdN)
if !ok {
if useDashN {
log.Printf("compiler output differs, with optimizers disabled (-N)")
} else {
log.Printf("compiler output differs")
}
if dashcIndex >= 0 {
cmd[dashcIndex] = "-c=1"
}
cmd = injectflags(cmd, []string{"-v", "-m=2"}, useDashN)
break
}
if dashcIndex >= 0 {
cmd[dashcIndex] = "-c=1"
}
cmd = injectflags(cmd, []string{"-v", "-m=2"}, false)
log.Printf("compiler output differs, only with optimizers enabled")
case tool == "asm" || strings.HasSuffix(tool, "a"): // assembler
log.Printf("assembler output differs")
case tool == "link" || strings.HasSuffix(tool, "l"): // linker
log.Printf("linker output differs")
extra = "-v=2"
}
cmdS := injectflags(cmd, []string{extra}, false)
outfile, _ = cmpRun(true, cmdS)
fmt.Fprintf(os.Stderr, "\n%s\n", compareLogs(outfile))
os.Exit(2)
}
func injectflags(cmd []string, extra []string, addDashN bool) []string {
x := []string{cmd[0]}
if addDashN {
x = append(x, "-N")
}
x = append(x, extra...)
x = append(x, cmd[1:]...)
return x
}
func cmpRun(keepLog bool, cmd []string) (outfile string, match bool) {
cmdStash := make([]string, len(cmd))
copy(cmdStash, cmd)
cmdStash[0] = toolStash
for i, arg := range cmdStash {
if arg == "-o" {
outfile = cmdStash[i+1]
cmdStash[i+1] += ".stash"
break
}
if strings.HasSuffix(arg, ".s") || strings.HasSuffix(arg, ".go") && '0' <= tool[0] && tool[0] <= '9' {
outfile = filepath.Base(arg[:strings.LastIndex(arg, ".")] + "." + tool[:1])
cmdStash = append([]string{cmdStash[0], "-o", outfile + ".stash"}, cmdStash[1:]...)
break
}
}
if outfile == "" {
log.Fatalf("cannot determine output file for command: %s", strings.Join(cmd, " "))
}
if *norun {
fmt.Printf("%s\n", strings.Join(cmd, " "))
fmt.Printf("%s\n", strings.Join(cmdStash, " "))
os.Exit(0)
}
out, err := runCmd(cmd, keepLog, outfile+".log")
if err != nil {
log.Printf("running: %s", strings.Join(cmd, " "))
os.Stderr.Write(out)
log.Fatal(err)
}
outStash, err := runCmd(cmdStash, keepLog, outfile+".stash.log")
if err != nil {
log.Printf("running: %s", strings.Join(cmdStash, " "))
log.Printf("installed tool succeeded but stashed tool failed.\n")
if len(out) > 0 {
log.Printf("installed tool output:")
os.Stderr.Write(out)
}
if len(outStash) > 0 {
log.Printf("stashed tool output:")
os.Stderr.Write(outStash)
}
log.Fatal(err)
}
return outfile, sameObject(outfile, outfile+".stash")
}
func sameObject(file1, file2 string) bool {
f1, err := os.Open(file1)
if err != nil {
log.Fatal(err)
}
defer f1.Close()
f2, err := os.Open(file2)
if err != nil {
log.Fatal(err)
}
defer f2.Close()
b1 := bufio.NewReader(f1)
b2 := bufio.NewReader(f2)
// Go object files and archives contain lines of the form
// go object
// By default, the version on development branches includes
// the Git hash and time stamp for the most recent commit.
// We allow the versions to differ.
if !skipVersion(b1, b2, file1, file2) {
return false
}
lastByte := byte(0)
for {
c1, err1 := b1.ReadByte()
c2, err2 := b2.ReadByte()
if err1 == io.EOF && err2 == io.EOF {
return true
}
if err1 != nil {
log.Fatalf("reading %s: %v", file1, err1)
}
if err2 != nil {
log.Fatalf("reading %s: %v", file2, err2)
}
if c1 != c2 {
return false
}
if lastByte == '`' && c1 == '\n' {
if !skipVersion(b1, b2, file1, file2) {
return false
}
}
lastByte = c1
}
}
func skipVersion(b1, b2 *bufio.Reader, file1, file2 string) bool {
// Consume "go object " prefix, if there.
prefix := "go object "
for i := 0; i < len(prefix); i++ {
c1, err1 := b1.ReadByte()
c2, err2 := b2.ReadByte()
if err1 == io.EOF && err2 == io.EOF {
return true
}
if err1 != nil {
log.Fatalf("reading %s: %v", file1, err1)
}
if err2 != nil {
log.Fatalf("reading %s: %v", file2, err2)
}
if c1 != c2 {
return false
}
if c1 != prefix[i] {
return true // matching bytes, just not a version
}
}
// Keep comparing until second space.
// Must continue to match.
// If we see a \n, it's not a version string after all.
for numSpace := 0; numSpace < 2; {
c1, err1 := b1.ReadByte()
c2, err2 := b2.ReadByte()
if err1 == io.EOF && err2 == io.EOF {
return true
}
if err1 != nil {
log.Fatalf("reading %s: %v", file1, err1)
}
if err2 != nil {
log.Fatalf("reading %s: %v", file2, err2)
}
if c1 != c2 {
return false
}
if c1 == '\n' {
return true
}
if c1 == ' ' {
numSpace++
}
}
// Have now seen 'go object goos goarch ' in both files.
// Now they're allowed to diverge, until the \n, which
// must be present.
for {
c1, err1 := b1.ReadByte()
if err1 == io.EOF {
log.Fatalf("reading %s: unexpected EOF", file1)
}
if err1 != nil {
log.Fatalf("reading %s: %v", file1, err1)
}
if c1 == '\n' {
break
}
}
for {
c2, err2 := b2.ReadByte()
if err2 == io.EOF {
log.Fatalf("reading %s: unexpected EOF", file2)
}
if err2 != nil {
log.Fatalf("reading %s: %v", file2, err2)
}
if c2 == '\n' {
break
}
}
// Consumed "matching" versions from both.
return true
}
func runCmd(cmd []string, keepLog bool, logName string) (output []byte, err error) {
if *verbose {
log.Print(strings.Join(cmd, " "))
}
if *timing {
t0 := time.Now()
defer func() {
log.Printf("%.3fs elapsed # %s\n", time.Since(t0).Seconds(), strings.Join(cmd, " "))
}()
}
xcmd := exec.Command(exeName(cmd[0]), cmd[1:]...)
if !keepLog {
return xcmd.CombinedOutput()
}
f, err := os.Create(logName)
if err != nil {
log.Fatal(err)
}
fmt.Fprintf(f, "GOOS=%s GOARCH=%s %s\n", os.Getenv("GOOS"), os.Getenv("GOARCH"), strings.Join(cmd, " "))
xcmd.Stdout = f
xcmd.Stderr = f
defer f.Close()
return nil, xcmd.Run()
}
func save() {
if err := os.MkdirAll(stashDir, 0777); err != nil {
log.Fatal(err)
}
toolDir := filepath.Join(goroot, fmt.Sprintf("pkg/tool/%s_%s", runtime.GOOS, runtime.GOARCH))
files, err := os.ReadDir(toolDir)
if err != nil {
log.Fatal(err)
}
for _, file := range files {
info, err := file.Info()
if err != nil {
log.Fatal(err)
}
if shouldSave(file.Name()) && info.Mode().IsRegular() {
cp(filepath.Join(toolDir, file.Name()), filepath.Join(stashDir, file.Name()))
}
}
for _, name := range binTools {
if !shouldSave(name) {
continue
}
bin := exeName(name)
src := filepath.Join(binDir, bin)
if _, err := os.Stat(src); err == nil {
cp(src, filepath.Join(stashDir, bin))
}
}
checkShouldSave()
}
func restore() {
files, err := os.ReadDir(stashDir)
if err != nil {
log.Fatal(err)
}
for _, file := range files {
info, err := file.Info()
if err != nil {
log.Fatal(err)
}
if shouldSave(file.Name()) && info.Mode().IsRegular() {
targ := toolDir
if isBinTool(file.Name()) {
targ = binDir
}
cp(filepath.Join(stashDir, file.Name()), filepath.Join(targ, file.Name()))
}
}
checkShouldSave()
}
func shouldSave(name string) bool {
if len(cmd) == 1 {
return true
}
ok := false
for i, arg := range cmd {
if i > 0 && name == arg {
ok = true
cmd[i] = "DONE"
}
}
return ok
}
func checkShouldSave() {
var missing []string
for _, arg := range cmd[1:] {
if arg != "DONE" {
missing = append(missing, arg)
}
}
if len(missing) > 0 {
log.Fatalf("%s did not find tools: %s", cmd[0], strings.Join(missing, " "))
}
}
func cp(src, dst string) {
if *verbose {
fmt.Printf("cp %s %s\n", src, dst)
}
data, err := os.ReadFile(src)
if err != nil {
log.Fatal(err)
}
if err := os.WriteFile(dst, data, 0777); err != nil {
log.Fatal(err)
}
}
func exeName(name string) string {
if runtime.GOOS == "windows" {
return name + ".exe"
}
return name
}
================================================
FILE: codereview.cfg
================================================
issuerepo: golang/go
================================================
FILE: container/intsets/export_test.go
================================================
// Copyright 2014 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package intsets
// Backdoor for testing.
func (s *Sparse) Check() error { return s.check() }
================================================
FILE: container/intsets/sparse.go
================================================
// Copyright 2014 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package intsets provides Sparse, a compact and fast representation
// for sparse sets of int values.
//
// The time complexity of the operations Len, Insert, Remove and Has
// is in O(n) but in practice those methods are faster and more
// space-efficient than equivalent operations on sets based on the Go
// map type. The IsEmpty, Min, Max, Clear and TakeMin operations
// require constant time.
package intsets // import "golang.org/x/tools/container/intsets"
// TODO(adonovan):
// - Add InsertAll(...int), RemoveAll(...int)
// - Add 'bool changed' results for {Intersection,Difference}With too.
//
// TODO(adonovan): implement Dense, a dense bit vector with a similar API.
// The space usage would be proportional to Max(), not Len(), and the
// implementation would be based upon big.Int.
//
// TODO(adonovan): opt: make UnionWith and Difference faster.
// These are the hot-spots for go/pointer.
import (
"bytes"
"fmt"
"math/bits"
)
// A Sparse is a set of int values.
// Sparse operations (even queries) are not concurrency-safe.
//
// The zero value for Sparse is a valid empty set.
//
// Sparse sets must be copied using the Copy method, not by assigning
// a Sparse value.
type Sparse struct {
// An uninitialized Sparse represents an empty set.
// An empty set may also be represented by
// root.next == root.prev == &root.
//
// The root is always the block with the smallest offset.
// It can be empty, but only if it is the only block; in that case, offset is
// MaxInt (which is not a valid offset).
root block
}
type word uintptr
const (
_m = ^word(0)
bitsPerWord = 8 << (_m>>8&1 + _m>>16&1 + _m>>32&1)
bitsPerBlock = 256 // optimal value for go/pointer solver performance
wordsPerBlock = bitsPerBlock / bitsPerWord
)
// Limit values of implementation-specific int type.
const (
MaxInt = int(^uint(0) >> 1)
MinInt = -MaxInt - 1
)
// popcount returns the number of set bits in w.
func popcount(x word) int {
// Avoid OnesCount(uint): don't assume uint = uintptr.
if bitsPerWord == 32 {
return bits.OnesCount32(uint32(x))
} else {
return bits.OnesCount64(uint64(x))
}
}
// nlz returns the number of leading zeros of x.
func nlz(x word) int {
// Avoid LeadingZeros(uint): don't assume uint = uintptr.
if bitsPerWord == 32 {
return bits.LeadingZeros32(uint32(x))
} else {
return bits.LeadingZeros64(uint64(x))
}
}
// ntz returns the number of trailing zeros of x.
func ntz(x word) int {
// Avoid TrailingZeros(uint): don't assume uint = uintptr.
if bitsPerWord == 32 {
return bits.TrailingZeros32(uint32(x))
} else {
return bits.TrailingZeros64(uint64(x))
}
}
// -- block ------------------------------------------------------------
// A set is represented as a circular doubly-linked list of blocks,
// each containing an offset and a bit array of fixed size
// bitsPerBlock; the blocks are ordered by increasing offset.
//
// The set contains an element x iff the block whose offset is x - (x
// mod bitsPerBlock) has the bit (x mod bitsPerBlock) set, where mod
// is the Euclidean remainder.
//
// A block may only be empty transiently.
type block struct {
offset int // offset mod bitsPerBlock == 0
bits [wordsPerBlock]word // contains at least one set bit
next, prev *block // doubly-linked list of blocks
}
// wordMask returns the word index (in block.bits)
// and single-bit mask for the block's ith bit.
func wordMask(i uint) (w uint, mask word) {
w = i / bitsPerWord
mask = 1 << (i % bitsPerWord)
return
}
// insert sets the block b's ith bit and
// returns true if it was not already set.
func (b *block) insert(i uint) bool {
w, mask := wordMask(i)
if b.bits[w]&mask == 0 {
b.bits[w] |= mask
return true
}
return false
}
// remove clears the block's ith bit and
// returns true if the bit was previously set.
// NB: may leave the block empty.
func (b *block) remove(i uint) bool {
w, mask := wordMask(i)
if b.bits[w]&mask != 0 {
b.bits[w] &^= mask
return true
}
return false
}
// has reports whether the block's ith bit is set.
func (b *block) has(i uint) bool {
w, mask := wordMask(i)
return b.bits[w]&mask != 0
}
// empty reports whether b.len()==0, but more efficiently.
func (b *block) empty() bool {
for _, w := range b.bits {
if w != 0 {
return false
}
}
return true
}
// len returns the number of set bits in block b.
func (b *block) len() int {
var l int
for _, w := range b.bits {
l += popcount(w)
}
return l
}
// max returns the maximum element of the block.
// The block must not be empty.
func (b *block) max() int {
bi := b.offset + bitsPerBlock
// Decrement bi by number of high zeros in last.bits.
for i := len(b.bits) - 1; i >= 0; i-- {
if w := b.bits[i]; w != 0 {
return bi - nlz(w) - 1
}
bi -= bitsPerWord
}
panic("BUG: empty block")
}
// min returns the minimum element of the block,
// and also removes it if take is set.
// The block must not be initially empty.
// NB: may leave the block empty.
func (b *block) min(take bool) int {
for i, w := range b.bits {
if w != 0 {
tz := ntz(w)
if take {
b.bits[i] = w &^ (1 << uint(tz))
}
return b.offset + i*bitsPerWord + tz
}
}
panic("BUG: empty block")
}
// lowerBound returns the smallest element of the block that is greater than or
// equal to the element corresponding to the ith bit. If there is no such
// element, the second return value is false.
func (b *block) lowerBound(i uint) (int, bool) {
w := i / bitsPerWord
bit := i % bitsPerWord
if val := b.bits[w] >> bit; val != 0 {
return b.offset + int(i) + ntz(val), true
}
for w++; w < wordsPerBlock; w++ {
if val := b.bits[w]; val != 0 {
return b.offset + int(w*bitsPerWord) + ntz(val), true
}
}
return 0, false
}
// forEach calls f for each element of block b.
// f must not mutate b's enclosing Sparse.
func (b *block) forEach(f func(int)) {
for i, w := range b.bits {
offset := b.offset + i*bitsPerWord
for bi := 0; w != 0 && bi < bitsPerWord; bi++ {
if w&1 != 0 {
f(offset)
}
offset++
w >>= 1
}
}
}
// offsetAndBitIndex returns the offset of the block that would
// contain x and the bit index of x within that block.
func offsetAndBitIndex(x int) (int, uint) {
mod := x % bitsPerBlock
if mod < 0 {
// Euclidean (non-negative) remainder
mod += bitsPerBlock
}
return x - mod, uint(mod)
}
// -- Sparse --------------------------------------------------------------
// none is a shared, empty, sentinel block that indicates the end of a block
// list.
var none block
// Dummy type used to generate an implicit panic. This must be defined at the
// package level; if it is defined inside a function, it prevents the inlining
// of that function.
type to_copy_a_sparse_you_must_call_its_Copy_method struct{}
// init ensures s is properly initialized.
func (s *Sparse) init() {
root := &s.root
if root.next == nil {
root.offset = MaxInt
root.next = root
root.prev = root
} else if root.next.prev != root {
// Copying a Sparse x leads to pernicious corruption: the
// new Sparse y shares the old linked list, but iteration
// on y will never encounter &y.root so it goes into a
// loop. Fail fast before this occurs.
// We don't want to call panic here because it prevents the
// inlining of this function.
_ = (any(nil)).(to_copy_a_sparse_you_must_call_its_Copy_method)
}
}
func (s *Sparse) first() *block {
s.init()
if s.root.offset == MaxInt {
return &none
}
return &s.root
}
// next returns the next block in the list, or end if b is the last block.
func (s *Sparse) next(b *block) *block {
if b.next == &s.root {
return &none
}
return b.next
}
// IsEmpty reports whether the set s is empty.
func (s *Sparse) IsEmpty() bool {
return s.root.next == nil || s.root.offset == MaxInt
}
// Len returns the number of elements in the set s.
func (s *Sparse) Len() int {
var l int
for b := s.first(); b != &none; b = s.next(b) {
l += b.len()
}
return l
}
// Max returns the maximum element of the set s, or MinInt if s is empty.
func (s *Sparse) Max() int {
if s.IsEmpty() {
return MinInt
}
return s.root.prev.max()
}
// Min returns the minimum element of the set s, or MaxInt if s is empty.
func (s *Sparse) Min() int {
if s.IsEmpty() {
return MaxInt
}
return s.root.min(false)
}
// LowerBound returns the smallest element >= x, or MaxInt if there is no such
// element.
func (s *Sparse) LowerBound(x int) int {
offset, i := offsetAndBitIndex(x)
for b := s.first(); b != &none; b = s.next(b) {
if b.offset > offset {
return b.min(false)
}
if b.offset == offset {
if y, ok := b.lowerBound(i); ok {
return y
}
}
}
return MaxInt
}
// block returns the block that would contain offset,
// or nil if s contains no such block.
// Precondition: offset is a multiple of bitsPerBlock.
func (s *Sparse) block(offset int) *block {
for b := s.first(); b != &none && b.offset <= offset; b = s.next(b) {
if b.offset == offset {
return b
}
}
return nil
}
// Insert adds x to the set s, and reports whether the set grew.
func (s *Sparse) Insert(x int) bool {
offset, i := offsetAndBitIndex(x)
b := s.first()
for ; b != &none && b.offset <= offset; b = s.next(b) {
if b.offset == offset {
return b.insert(i)
}
}
// Insert new block before b.
new := s.insertBlockBefore(b)
new.offset = offset
return new.insert(i)
}
// removeBlock removes a block and returns the block that followed it (or end if
// it was the last block).
func (s *Sparse) removeBlock(b *block) *block {
if b != &s.root {
b.prev.next = b.next
b.next.prev = b.prev
if b.next == &s.root {
return &none
}
return b.next
}
first := s.root.next
if first == &s.root {
// This was the only block.
s.Clear()
return &none
}
s.root.offset = first.offset
s.root.bits = first.bits
if first.next == &s.root {
// Single block remaining.
s.root.next = &s.root
s.root.prev = &s.root
} else {
s.root.next = first.next
first.next.prev = &s.root
}
return &s.root
}
// Remove removes x from the set s, and reports whether the set shrank.
func (s *Sparse) Remove(x int) bool {
offset, i := offsetAndBitIndex(x)
if b := s.block(offset); b != nil {
if !b.remove(i) {
return false
}
if b.empty() {
s.removeBlock(b)
}
return true
}
return false
}
// Clear removes all elements from the set s.
func (s *Sparse) Clear() {
s.root = block{
offset: MaxInt,
next: &s.root,
prev: &s.root,
}
}
// If set s is non-empty, TakeMin sets *p to the minimum element of
// the set s, removes that element from the set and returns true.
// Otherwise, it returns false and *p is undefined.
//
// This method may be used for iteration over a worklist like so:
//
// var x int
// for worklist.TakeMin(&x) { use(x) }
func (s *Sparse) TakeMin(p *int) bool {
if s.IsEmpty() {
return false
}
*p = s.root.min(true)
if s.root.empty() {
s.removeBlock(&s.root)
}
return true
}
// Has reports whether x is an element of the set s.
func (s *Sparse) Has(x int) bool {
offset, i := offsetAndBitIndex(x)
if b := s.block(offset); b != nil {
return b.has(i)
}
return false
}
// forEach applies function f to each element of the set s in order.
//
// f must not mutate s. Consequently, forEach is not safe to expose
// to clients. In any case, using "range s.AppendTo()" allows more
// natural control flow with continue/break/return.
func (s *Sparse) forEach(f func(int)) {
for b := s.first(); b != &none; b = s.next(b) {
b.forEach(f)
}
}
// Copy sets s to the value of x.
func (s *Sparse) Copy(x *Sparse) {
if s == x {
return
}
xb := x.first()
sb := s.first()
for xb != &none {
if sb == &none {
sb = s.insertBlockBefore(sb)
}
sb.offset = xb.offset
sb.bits = xb.bits
xb = x.next(xb)
sb = s.next(sb)
}
s.discardTail(sb)
}
// insertBlockBefore returns a new block, inserting it before next.
// If next is the root, the root is replaced. If next is end, the block is
// inserted at the end.
func (s *Sparse) insertBlockBefore(next *block) *block {
if s.IsEmpty() {
if next != &none {
panic("BUG: passed block with empty set")
}
return &s.root
}
if next == &s.root {
// Special case: we need to create a new block that will become the root
// block.The old root block becomes the second block.
second := s.root
s.root = block{
next: &second,
}
if second.next == &s.root {
s.root.prev = &second
} else {
s.root.prev = second.prev
second.next.prev = &second
second.prev = &s.root
}
return &s.root
}
if next == &none {
// Insert before root.
next = &s.root
}
b := new(block)
b.next = next
b.prev = next.prev
b.prev.next = b
next.prev = b
return b
}
// discardTail removes block b and all its successors from s.
func (s *Sparse) discardTail(b *block) {
if b != &none {
if b == &s.root {
s.Clear()
} else {
b.prev.next = &s.root
s.root.prev = b.prev
}
}
}
// IntersectionWith sets s to the intersection s ∩ x.
func (s *Sparse) IntersectionWith(x *Sparse) {
if s == x {
return
}
xb := x.first()
sb := s.first()
for xb != &none && sb != &none {
switch {
case xb.offset < sb.offset:
xb = x.next(xb)
case xb.offset > sb.offset:
sb = s.removeBlock(sb)
default:
var sum word
for i := range sb.bits {
r := xb.bits[i] & sb.bits[i]
sb.bits[i] = r
sum |= r
}
if sum != 0 {
sb = s.next(sb)
} else {
// sb will be overwritten or removed
}
xb = x.next(xb)
}
}
s.discardTail(sb)
}
// Intersection sets s to the intersection x ∩ y.
func (s *Sparse) Intersection(x, y *Sparse) {
switch {
case s == x:
s.IntersectionWith(y)
return
case s == y:
s.IntersectionWith(x)
return
case x == y:
s.Copy(x)
return
}
xb := x.first()
yb := y.first()
sb := s.first()
for xb != &none && yb != &none {
switch {
case xb.offset < yb.offset:
xb = x.next(xb)
continue
case xb.offset > yb.offset:
yb = y.next(yb)
continue
}
if sb == &none {
sb = s.insertBlockBefore(sb)
}
sb.offset = xb.offset
var sum word
for i := range sb.bits {
r := xb.bits[i] & yb.bits[i]
sb.bits[i] = r
sum |= r
}
if sum != 0 {
sb = s.next(sb)
} else {
// sb will be overwritten or removed
}
xb = x.next(xb)
yb = y.next(yb)
}
s.discardTail(sb)
}
// Intersects reports whether s ∩ x ≠ ∅.
func (s *Sparse) Intersects(x *Sparse) bool {
sb := s.first()
xb := x.first()
for sb != &none && xb != &none {
switch {
case xb.offset < sb.offset:
xb = x.next(xb)
case xb.offset > sb.offset:
sb = s.next(sb)
default:
for i := range sb.bits {
if sb.bits[i]&xb.bits[i] != 0 {
return true
}
}
sb = s.next(sb)
xb = x.next(xb)
}
}
return false
}
// UnionWith sets s to the union s ∪ x, and reports whether s grew.
func (s *Sparse) UnionWith(x *Sparse) bool {
if s == x {
return false
}
var changed bool
xb := x.first()
sb := s.first()
for xb != &none {
if sb != &none && sb.offset == xb.offset {
for i := range xb.bits {
union := sb.bits[i] | xb.bits[i]
if sb.bits[i] != union {
sb.bits[i] = union
changed = true
}
}
xb = x.next(xb)
} else if sb == &none || sb.offset > xb.offset {
sb = s.insertBlockBefore(sb)
sb.offset = xb.offset
sb.bits = xb.bits
changed = true
xb = x.next(xb)
}
sb = s.next(sb)
}
return changed
}
// Union sets s to the union x ∪ y.
func (s *Sparse) Union(x, y *Sparse) {
switch {
case x == y:
s.Copy(x)
return
case s == x:
s.UnionWith(y)
return
case s == y:
s.UnionWith(x)
return
}
xb := x.first()
yb := y.first()
sb := s.first()
for xb != &none || yb != &none {
if sb == &none {
sb = s.insertBlockBefore(sb)
}
switch {
case yb == &none || (xb != &none && xb.offset < yb.offset):
sb.offset = xb.offset
sb.bits = xb.bits
xb = x.next(xb)
case xb == &none || (yb != &none && yb.offset < xb.offset):
sb.offset = yb.offset
sb.bits = yb.bits
yb = y.next(yb)
default:
sb.offset = xb.offset
for i := range xb.bits {
sb.bits[i] = xb.bits[i] | yb.bits[i]
}
xb = x.next(xb)
yb = y.next(yb)
}
sb = s.next(sb)
}
s.discardTail(sb)
}
// DifferenceWith sets s to the difference s ∖ x.
func (s *Sparse) DifferenceWith(x *Sparse) {
if s == x {
s.Clear()
return
}
xb := x.first()
sb := s.first()
for xb != &none && sb != &none {
switch {
case xb.offset > sb.offset:
sb = s.next(sb)
case xb.offset < sb.offset:
xb = x.next(xb)
default:
var sum word
for i := range sb.bits {
r := sb.bits[i] & ^xb.bits[i]
sb.bits[i] = r
sum |= r
}
if sum == 0 {
sb = s.removeBlock(sb)
} else {
sb = s.next(sb)
}
xb = x.next(xb)
}
}
}
// Difference sets s to the difference x ∖ y.
func (s *Sparse) Difference(x, y *Sparse) {
switch {
case x == y:
s.Clear()
return
case s == x:
s.DifferenceWith(y)
return
case s == y:
var y2 Sparse
y2.Copy(y)
s.Difference(x, &y2)
return
}
xb := x.first()
yb := y.first()
sb := s.first()
for xb != &none && yb != &none {
if xb.offset > yb.offset {
// y has block, x has &none
yb = y.next(yb)
continue
}
if sb == &none {
sb = s.insertBlockBefore(sb)
}
sb.offset = xb.offset
switch {
case xb.offset < yb.offset:
// x has block, y has &none
sb.bits = xb.bits
sb = s.next(sb)
default:
// x and y have corresponding blocks
var sum word
for i := range sb.bits {
r := xb.bits[i] & ^yb.bits[i]
sb.bits[i] = r
sum |= r
}
if sum != 0 {
sb = s.next(sb)
} else {
// sb will be overwritten or removed
}
yb = y.next(yb)
}
xb = x.next(xb)
}
for xb != &none {
if sb == &none {
sb = s.insertBlockBefore(sb)
}
sb.offset = xb.offset
sb.bits = xb.bits
sb = s.next(sb)
xb = x.next(xb)
}
s.discardTail(sb)
}
// SymmetricDifferenceWith sets s to the symmetric difference s ∆ x.
func (s *Sparse) SymmetricDifferenceWith(x *Sparse) {
if s == x {
s.Clear()
return
}
sb := s.first()
xb := x.first()
for xb != &none && sb != &none {
switch {
case sb.offset < xb.offset:
sb = s.next(sb)
case xb.offset < sb.offset:
nb := s.insertBlockBefore(sb)
nb.offset = xb.offset
nb.bits = xb.bits
xb = x.next(xb)
default:
var sum word
for i := range sb.bits {
r := sb.bits[i] ^ xb.bits[i]
sb.bits[i] = r
sum |= r
}
if sum == 0 {
sb = s.removeBlock(sb)
} else {
sb = s.next(sb)
}
xb = x.next(xb)
}
}
for xb != &none { // append the tail of x to s
sb = s.insertBlockBefore(sb)
sb.offset = xb.offset
sb.bits = xb.bits
sb = s.next(sb)
xb = x.next(xb)
}
}
// SymmetricDifference sets s to the symmetric difference x ∆ y.
func (s *Sparse) SymmetricDifference(x, y *Sparse) {
switch {
case x == y:
s.Clear()
return
case s == x:
s.SymmetricDifferenceWith(y)
return
case s == y:
s.SymmetricDifferenceWith(x)
return
}
sb := s.first()
xb := x.first()
yb := y.first()
for xb != &none && yb != &none {
if sb == &none {
sb = s.insertBlockBefore(sb)
}
switch {
case yb.offset < xb.offset:
sb.offset = yb.offset
sb.bits = yb.bits
sb = s.next(sb)
yb = y.next(yb)
case xb.offset < yb.offset:
sb.offset = xb.offset
sb.bits = xb.bits
sb = s.next(sb)
xb = x.next(xb)
default:
var sum word
for i := range sb.bits {
r := xb.bits[i] ^ yb.bits[i]
sb.bits[i] = r
sum |= r
}
if sum != 0 {
sb.offset = xb.offset
sb = s.next(sb)
}
xb = x.next(xb)
yb = y.next(yb)
}
}
for xb != &none { // append the tail of x to s
if sb == &none {
sb = s.insertBlockBefore(sb)
}
sb.offset = xb.offset
sb.bits = xb.bits
sb = s.next(sb)
xb = x.next(xb)
}
for yb != &none { // append the tail of y to s
if sb == &none {
sb = s.insertBlockBefore(sb)
}
sb.offset = yb.offset
sb.bits = yb.bits
sb = s.next(sb)
yb = y.next(yb)
}
s.discardTail(sb)
}
// SubsetOf reports whether s ∖ x = ∅.
func (s *Sparse) SubsetOf(x *Sparse) bool {
if s == x {
return true
}
sb := s.first()
xb := x.first()
for sb != &none {
switch {
case xb == &none || xb.offset > sb.offset:
return false
case xb.offset < sb.offset:
xb = x.next(xb)
default:
for i := range sb.bits {
if sb.bits[i]&^xb.bits[i] != 0 {
return false
}
}
sb = s.next(sb)
xb = x.next(xb)
}
}
return true
}
// Equals reports whether the sets s and t have the same elements.
func (s *Sparse) Equals(t *Sparse) bool {
if s == t {
return true
}
sb := s.first()
tb := t.first()
for {
switch {
case sb == &none && tb == &none:
return true
case sb == &none || tb == &none:
return false
case sb.offset != tb.offset:
return false
case sb.bits != tb.bits:
return false
}
sb = s.next(sb)
tb = t.next(tb)
}
}
// String returns a human-readable description of the set s.
func (s *Sparse) String() string {
var buf bytes.Buffer
buf.WriteByte('{')
s.forEach(func(x int) {
if buf.Len() > 1 {
buf.WriteByte(' ')
}
fmt.Fprintf(&buf, "%d", x)
})
buf.WriteByte('}')
return buf.String()
}
// BitString returns the set as a string of 1s and 0s denoting the sum
// of the i'th powers of 2, for each i in s. A radix point, always
// preceded by a digit, appears if the sum is non-integral.
//
// Examples:
//
// {}.BitString() = "0"
// {4,5}.BitString() = "110000"
// {-3}.BitString() = "0.001"
// {-3,0,4,5}.BitString() = "110001.001"
func (s *Sparse) BitString() string {
if s.IsEmpty() {
return "0"
}
min, max := s.Min(), s.Max()
var nbytes int
if max > 0 {
nbytes = max
}
nbytes++ // zero bit
radix := nbytes
if min < 0 {
nbytes += len(".") - min
}
b := make([]byte, nbytes)
for i := range b {
b[i] = '0'
}
if radix < nbytes {
b[radix] = '.'
}
s.forEach(func(x int) {
if x >= 0 {
x += len(".")
}
b[radix-x] = '1'
})
return string(b)
}
// GoString returns a string showing the internal representation of
// the set s.
func (s *Sparse) GoString() string {
var buf bytes.Buffer
for b := s.first(); b != &none; b = s.next(b) {
fmt.Fprintf(&buf, "block %p {offset=%d next=%p prev=%p",
b, b.offset, b.next, b.prev)
for _, w := range b.bits {
fmt.Fprintf(&buf, " 0%016x", w)
}
fmt.Fprintf(&buf, "}\n")
}
return buf.String()
}
// AppendTo returns the result of appending the elements of s to slice
// in order.
func (s *Sparse) AppendTo(slice []int) []int {
s.forEach(func(x int) {
slice = append(slice, x)
})
return slice
}
// -- Testing/debugging ------------------------------------------------
// check returns an error if the representation invariants of s are violated.
// (unused; retained for debugging)
func (s *Sparse) check() error {
s.init()
if s.root.empty() {
// An empty set must have only the root block with offset MaxInt.
if s.root.next != &s.root {
return fmt.Errorf("multiple blocks with empty root block")
}
if s.root.offset != MaxInt {
return fmt.Errorf("empty set has offset %d, should be MaxInt", s.root.offset)
}
return nil
}
for b := s.first(); ; b = s.next(b) {
if b.offset%bitsPerBlock != 0 {
return fmt.Errorf("bad offset modulo: %d", b.offset)
}
if b.empty() {
return fmt.Errorf("empty block")
}
if b.prev.next != b {
return fmt.Errorf("bad prev.next link")
}
if b.next.prev != b {
return fmt.Errorf("bad next.prev link")
}
if b.next == &s.root {
break
}
if b.offset >= b.next.offset {
return fmt.Errorf("bad offset order: b.offset=%d, b.next.offset=%d",
b.offset, b.next.offset)
}
}
return nil
}
================================================
FILE: container/intsets/sparse_test.go
================================================
// Copyright 2014 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package intsets_test
import (
"fmt"
"log"
"math/rand"
"sort"
"strings"
"testing"
"golang.org/x/tools/container/intsets"
)
func TestBasics(t *testing.T) {
var s intsets.Sparse
if len := s.Len(); len != 0 {
t.Errorf("Len({}): got %d, want 0", len)
}
if s := s.String(); s != "{}" {
t.Errorf("String({}): got %q, want \"{}\"", s)
}
if s.Has(3) {
t.Errorf("Has(3): got true, want false")
}
if err := s.Check(); err != nil {
t.Error(err)
}
if !s.Insert(3) {
t.Errorf("Insert(3): got false, want true")
}
if max := s.Max(); max != 3 {
t.Errorf("Max: got %d, want 3", max)
}
if !s.Insert(435) {
t.Errorf("Insert(435): got false, want true")
}
if s := s.String(); s != "{3 435}" {
t.Errorf("String({3 435}): got %q, want \"{3 435}\"", s)
}
if max := s.Max(); max != 435 {
t.Errorf("Max: got %d, want 435", max)
}
if len := s.Len(); len != 2 {
t.Errorf("Len: got %d, want 2", len)
}
if !s.Remove(435) {
t.Errorf("Remove(435): got false, want true")
}
if s := s.String(); s != "{3}" {
t.Errorf("String({3}): got %q, want \"{3}\"", s)
}
}
// Insert, Len, IsEmpty, Hash, Clear, AppendTo.
func TestMoreBasics(t *testing.T) {
set := new(intsets.Sparse)
set.Insert(456)
set.Insert(123)
set.Insert(789)
if set.Len() != 3 {
t.Errorf("%s.Len: got %d, want 3", set, set.Len())
}
if set.IsEmpty() {
t.Errorf("%s.IsEmpty: got true", set)
}
if !set.Has(123) {
t.Errorf("%s.Has(123): got false", set)
}
if set.Has(1234) {
t.Errorf("%s.Has(1234): got true", set)
}
got := set.AppendTo([]int{-1})
if want := []int{-1, 123, 456, 789}; fmt.Sprint(got) != fmt.Sprint(want) {
t.Errorf("%s.AppendTo: got %v, want %v", set, got, want)
}
set.Clear()
if set.Len() != 0 {
t.Errorf("Clear: got %d, want 0", set.Len())
}
if !set.IsEmpty() {
t.Errorf("IsEmpty: got false")
}
if set.Has(123) {
t.Errorf("%s.Has: got false", set)
}
}
func TestTakeMin(t *testing.T) {
var set intsets.Sparse
set.Insert(456)
set.Insert(123)
set.Insert(789)
set.Insert(-123)
var got int
for i, want := range []int{-123, 123, 456, 789} {
if !set.TakeMin(&got) || got != want {
t.Errorf("TakeMin #%d: got %d, want %d", i, got, want)
}
}
if set.TakeMin(&got) {
t.Errorf("%s.TakeMin returned true", &set)
}
if err := set.Check(); err != nil {
t.Fatalf("check: %s: %#v", err, &set)
}
}
func TestMinAndMax(t *testing.T) {
values := []int{0, 456, 123, 789, -123} // elt 0 => empty set
wantMax := []int{intsets.MinInt, 456, 456, 789, 789}
wantMin := []int{intsets.MaxInt, 456, 123, 123, -123}
var set intsets.Sparse
for i, x := range values {
if i != 0 {
set.Insert(x)
}
if got, want := set.Min(), wantMin[i]; got != want {
t.Errorf("Min #%d: got %d, want %d", i, got, want)
}
if got, want := set.Max(), wantMax[i]; got != want {
t.Errorf("Max #%d: got %d, want %d", i, got, want)
}
}
set.Insert(intsets.MinInt)
if got, want := set.Min(), intsets.MinInt; got != want {
t.Errorf("Min: got %d, want %d", got, want)
}
set.Insert(intsets.MaxInt)
if got, want := set.Max(), intsets.MaxInt; got != want {
t.Errorf("Max: got %d, want %d", got, want)
}
}
func TestEquals(t *testing.T) {
var setX intsets.Sparse
setX.Insert(456)
setX.Insert(123)
setX.Insert(789)
if !setX.Equals(&setX) {
t.Errorf("Equals(%s, %s): got false", &setX, &setX)
}
var setY intsets.Sparse
setY.Insert(789)
setY.Insert(456)
setY.Insert(123)
if !setX.Equals(&setY) {
t.Errorf("Equals(%s, %s): got false", &setX, &setY)
}
setY.Insert(1)
if setX.Equals(&setY) {
t.Errorf("Equals(%s, %s): got true", &setX, &setY)
}
var empty intsets.Sparse
if setX.Equals(&empty) {
t.Errorf("Equals(%s, %s): got true", &setX, &empty)
}
// Edge case: some block (with offset=0) appears in X but not Y.
setY.Remove(123)
if setX.Equals(&setY) {
t.Errorf("Equals(%s, %s): got true", &setX, &setY)
}
}
// A pset is a parallel implementation of a set using both an intsets.Sparse
// and a built-in hash map.
type pset struct {
hash map[int]bool
bits intsets.Sparse
}
func makePset() *pset {
return &pset{hash: make(map[int]bool)}
}
func (set *pset) add(n int) {
prev := len(set.hash)
set.hash[n] = true
grewA := len(set.hash) > prev
grewB := set.bits.Insert(n)
if grewA != grewB {
panic(fmt.Sprintf("add(%d): grewA=%t grewB=%t", n, grewA, grewB))
}
}
func (set *pset) remove(n int) {
prev := len(set.hash)
delete(set.hash, n)
shrankA := len(set.hash) < prev
shrankB := set.bits.Remove(n)
if shrankA != shrankB {
panic(fmt.Sprintf("remove(%d): shrankA=%t shrankB=%t", n, shrankA, shrankB))
}
}
func (set *pset) check(t *testing.T, msg string) {
var eltsA []int
for elt := range set.hash {
eltsA = append(eltsA, int(elt))
}
sort.Ints(eltsA)
eltsB := set.bits.AppendTo(nil)
if a, b := fmt.Sprint(eltsA), fmt.Sprint(eltsB); a != b {
t.Errorf("check(%s): hash=%s bits=%s (%s)", msg, a, b, &set.bits)
}
if err := set.bits.Check(); err != nil {
t.Fatalf("Check(%s): %s: %#v", msg, err, &set.bits)
}
}
// randomPset returns a parallel set of random size and elements.
func randomPset(prng *rand.Rand, maxSize int) *pset {
set := makePset()
size := int(prng.Int()) % maxSize
for range size {
// TODO(adonovan): benchmark how performance varies
// with this sparsity parameter.
n := int(prng.Int()) % 10000
set.add(n)
}
return set
}
// TestRandomMutations performs the same random adds/removes on two
// set implementations and ensures that they compute the same result.
func TestRandomMutations(t *testing.T) {
const debug = false
set := makePset()
prng := rand.New(rand.NewSource(0))
for i := range 10000 {
n := int(prng.Int())%2000 - 1000
if i%2 == 0 {
if debug {
log.Printf("add %d", n)
}
set.add(n)
} else {
if debug {
log.Printf("remove %d", n)
}
set.remove(n)
}
if debug {
set.check(t, "post mutation")
}
}
set.check(t, "final")
if debug {
log.Print(&set.bits)
}
}
func TestLowerBound(t *testing.T) {
// Use random sets of sizes from 0 to about 4000.
prng := rand.New(rand.NewSource(0))
for i := range uint(12) {
x := randomPset(prng, 1<= j && e < found {
found = e
}
}
if res := x.bits.LowerBound(j); res != found {
t.Errorf("%s: LowerBound(%d)=%d, expected %d", &x.bits, j, res, found)
}
}
}
}
// TestSetOperations exercises classic set operations: ∩ , ∪, \.
func TestSetOperations(t *testing.T) {
prng := rand.New(rand.NewSource(0))
// Use random sets of sizes from 0 to about 4000.
// For each operator, we test variations such as
// Z.op(X, Y), Z.op(X, Z) and Z.op(Z, Y) to exercise
// the degenerate cases of each method implementation.
for i := range uint(12) {
X := randomPset(prng, 1< prelen) != changed {
t.Errorf("%s.UnionWith(%s) => %s, changed=%t", xstr, y, x, changed)
}
}
// The case marked "!" is a regression test for Issue 50352,
// which spuriously returned true when y ⊂ x.
// same block
checkUnionWith(setOf(1, 2), setOf(1, 2))
checkUnionWith(setOf(1, 2, 3), setOf(1, 2)) // !
checkUnionWith(setOf(1, 2), setOf(1, 2, 3))
checkUnionWith(setOf(1, 2), setOf())
// different blocks
checkUnionWith(setOf(1, 1000000), setOf(1, 1000000))
checkUnionWith(setOf(1, 2, 1000000), setOf(1, 2))
checkUnionWith(setOf(1, 2), setOf(1, 2, 1000000))
checkUnionWith(setOf(1, 1000000), setOf())
}
func TestIntersectionWith(t *testing.T) {
// Edge cases: the pairs (1,1), (1000,2000), (8000,4000)
// exercise the <, >, == cases in IntersectionWith that the
// TestSetOperations data is too dense to cover.
var X, Y intsets.Sparse
X.Insert(1)
X.Insert(1000)
X.Insert(8000)
Y.Insert(1)
Y.Insert(2000)
Y.Insert(4000)
X.IntersectionWith(&Y)
if got, want := X.String(), "{1}"; got != want {
t.Errorf("IntersectionWith: got %s, want %s", got, want)
}
}
func TestIntersects(t *testing.T) {
prng := rand.New(rand.NewSource(0))
for i := range uint(12) {
X, Y := randomPset(prng, 1< len(probe) {
b.Fatalf("%d hits, only %d probes", hits, len(probe))
}
}
}
func BenchmarkInsertProbeSparse_2_10(b *testing.B) {
benchmarkInsertProbeSparse(b, 2, 10)
}
func BenchmarkInsertProbeSparse_10_10(b *testing.B) {
benchmarkInsertProbeSparse(b, 10, 10)
}
func BenchmarkInsertProbeSparse_10_1000(b *testing.B) {
benchmarkInsertProbeSparse(b, 10, 1000)
}
func BenchmarkInsertProbeSparse_100_100(b *testing.B) {
benchmarkInsertProbeSparse(b, 100, 100)
}
func BenchmarkInsertProbeSparse_100_10000(b *testing.B) {
benchmarkInsertProbeSparse(b, 100, 1000)
}
func BenchmarkUnionDifferenceSparse(b *testing.B) {
prng := rand.New(rand.NewSource(0))
for b.Loop() {
var x, y, z intsets.Sparse
for i := range 1000 {
n := int(prng.Int()) % 100000
if i%2 == 0 {
x.Insert(n)
} else {
y.Insert(n)
}
}
z.Union(&x, &y)
z.Difference(&x, &y)
}
}
func BenchmarkUnionDifferenceHashTable(b *testing.B) {
prng := rand.New(rand.NewSource(0))
for b.Loop() {
x, y, z := make(map[int]bool), make(map[int]bool), make(map[int]bool)
for i := range 1000 {
n := int(prng.Int()) % 100000
if i%2 == 0 {
x[n] = true
} else {
y[n] = true
}
}
// union
for n := range x {
z[n] = true
}
for n := range y {
z[n] = true
}
// difference
z = make(map[int]bool)
for n := range y {
if !x[n] {
z[n] = true
}
}
}
}
func BenchmarkAppendTo(b *testing.B) {
prng := rand.New(rand.NewSource(0))
var x intsets.Sparse
for range 1000 {
x.Insert(int(prng.Int()) % 10000)
}
var space [1000]int
for b.Loop() {
x.AppendTo(space[:0])
}
}
================================================
FILE: copyright/copyright.go
================================================
// Copyright 2020 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package copyright checks that files have the correct copyright notices.
package copyright
import (
"go/ast"
"go/parser"
"go/token"
"io/fs"
"os"
"path/filepath"
"regexp"
"strings"
)
// (used only by tests)
func checkCopyright(dir string) ([]string, error) {
var files []string
err := filepath.WalkDir(dir, func(path string, d fs.DirEntry, err error) error {
if err != nil {
return err
}
if d.IsDir() {
// Skip directories like ".git".
if strings.HasPrefix(d.Name(), ".") && d.Name() != "." && d.Name() != ".." {
return filepath.SkipDir
}
// Skip any directory that starts with an underscore, as the go
// command would.
if strings.HasPrefix(d.Name(), "_") {
return filepath.SkipDir
}
return nil
}
needsCopyright, err := checkFile(dir, path)
if err != nil {
return err
}
if needsCopyright {
files = append(files, path)
}
return nil
})
return files, err
}
var copyrightRe = regexp.MustCompile(`Copyright \d{4} The Go Authors. All rights reserved.
Use of this source code is governed by a BSD-style
license that can be found in the LICENSE file.`)
func checkFile(toolsDir, filename string) (bool, error) {
// Only check Go files.
if !strings.HasSuffix(filename, ".go") {
return false, nil
}
// Don't check testdata files.
normalized := strings.TrimPrefix(filepath.ToSlash(filename), filepath.ToSlash(toolsDir))
if strings.Contains(normalized, "/testdata/") {
return false, nil
}
// goyacc is the only file with a different copyright header.
if strings.HasSuffix(normalized, "cmd/goyacc/yacc.go") {
return false, nil
}
content, err := os.ReadFile(filename)
if err != nil {
return false, err
}
fset := token.NewFileSet()
parsed, err := parser.ParseFile(fset, filename, content, parser.ParseComments)
if err != nil {
return false, err
}
// Don't require headers on generated files.
if ast.IsGenerated(parsed) {
return false, nil
}
shouldAddCopyright := true
for _, c := range parsed.Comments {
// The copyright should appear before the package declaration.
if c.Pos() > parsed.Package {
break
}
if copyrightRe.MatchString(c.Text()) {
shouldAddCopyright = false
break
}
}
return shouldAddCopyright, nil
}
================================================
FILE: copyright/copyright_test.go
================================================
// Copyright 2020 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package copyright
import (
"strings"
"testing"
)
func TestToolsCopyright(t *testing.T) {
files, err := checkCopyright("..")
if err != nil {
t.Fatal(err)
}
if len(files) > 0 {
t.Errorf("The following files are missing copyright notices:\n%s", strings.Join(files, "\n"))
}
}
================================================
FILE: cover/profile.go
================================================
// Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package cover provides support for parsing coverage profiles
// generated by "go test -coverprofile=cover.out".
package cover // import "golang.org/x/tools/cover"
import (
"bufio"
"errors"
"fmt"
"io"
"math"
"os"
"sort"
"strconv"
"strings"
)
// Profile represents the profiling data for a specific file.
type Profile struct {
FileName string
Mode string
Blocks []ProfileBlock
}
// ProfileBlock represents a single block of profiling data.
type ProfileBlock struct {
StartLine, StartCol int
EndLine, EndCol int
NumStmt, Count int
}
type byFileName []*Profile
func (p byFileName) Len() int { return len(p) }
func (p byFileName) Less(i, j int) bool { return p[i].FileName < p[j].FileName }
func (p byFileName) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
// ParseProfiles parses profile data in the specified file and returns a
// Profile for each source file described therein.
func ParseProfiles(fileName string) ([]*Profile, error) {
pf, err := os.Open(fileName)
if err != nil {
return nil, err
}
defer pf.Close()
return ParseProfilesFromReader(pf)
}
// ParseProfilesFromReader parses profile data from the Reader and
// returns a Profile for each source file described therein.
func ParseProfilesFromReader(rd io.Reader) ([]*Profile, error) {
// First line is "mode: foo", where foo is "set", "count", or "atomic".
// Rest of file is in the format
// encoding/base64/base64.go:34.44,37.40 3 1
// where the fields are: name.go:line.column,line.column numberOfStatements count
files := make(map[string]*Profile)
s := bufio.NewScanner(rd)
mode := ""
for s.Scan() {
line := s.Text()
if mode == "" {
const p = "mode: "
if !strings.HasPrefix(line, p) || line == p {
return nil, fmt.Errorf("bad mode line: %v", line)
}
mode = line[len(p):]
continue
}
fn, b, err := parseLine(line)
if err != nil {
return nil, fmt.Errorf("line %q doesn't match expected format: %v", line, err)
}
p := files[fn]
if p == nil {
p = &Profile{
FileName: fn,
Mode: mode,
}
files[fn] = p
}
p.Blocks = append(p.Blocks, b)
}
if err := s.Err(); err != nil {
return nil, err
}
for _, p := range files {
sort.Sort(blocksByStart(p.Blocks))
// Merge samples from the same location.
j := 1
for i := 1; i < len(p.Blocks); i++ {
b := p.Blocks[i]
last := p.Blocks[j-1]
if b.StartLine == last.StartLine &&
b.StartCol == last.StartCol &&
b.EndLine == last.EndLine &&
b.EndCol == last.EndCol {
if b.NumStmt != last.NumStmt {
return nil, fmt.Errorf("inconsistent NumStmt: changed from %d to %d", last.NumStmt, b.NumStmt)
}
if mode == "set" {
p.Blocks[j-1].Count |= b.Count
} else {
p.Blocks[j-1].Count += b.Count
}
continue
}
p.Blocks[j] = b
j++
}
p.Blocks = p.Blocks[:j]
}
// Generate a sorted slice.
profiles := make([]*Profile, 0, len(files))
for _, profile := range files {
profiles = append(profiles, profile)
}
sort.Sort(byFileName(profiles))
return profiles, nil
}
// parseLine parses a line from a coverage file.
// It is equivalent to the regex
// ^(.+):([0-9]+)\.([0-9]+),([0-9]+)\.([0-9]+) ([0-9]+) ([0-9]+)$
//
// However, it is much faster: https://golang.org/cl/179377
func parseLine(l string) (fileName string, block ProfileBlock, err error) {
end := len(l)
b := ProfileBlock{}
b.Count, end, err = seekBack(l, ' ', end, "Count")
if err != nil {
return "", b, err
}
b.NumStmt, end, err = seekBack(l, ' ', end, "NumStmt")
if err != nil {
return "", b, err
}
b.EndCol, end, err = seekBack(l, '.', end, "EndCol")
if err != nil {
return "", b, err
}
b.EndLine, end, err = seekBack(l, ',', end, "EndLine")
if err != nil {
return "", b, err
}
b.StartCol, end, err = seekBack(l, '.', end, "StartCol")
if err != nil {
return "", b, err
}
b.StartLine, end, err = seekBack(l, ':', end, "StartLine")
if err != nil {
return "", b, err
}
fn := l[0:end]
if fn == "" {
return "", b, errors.New("a FileName cannot be blank")
}
return fn, b, nil
}
// seekBack searches backwards from end to find sep in l, then returns the
// value between sep and end as an integer.
// If seekBack fails, the returned error will reference what.
func seekBack(l string, sep byte, end int, what string) (value int, nextSep int, err error) {
// Since we're seeking backwards and we know only ASCII is legal for these values,
// we can ignore the possibility of non-ASCII characters.
for start := end - 1; start >= 0; start-- {
if l[start] == sep {
i, err := strconv.Atoi(l[start+1 : end])
if err != nil {
return 0, 0, fmt.Errorf("couldn't parse %q: %v", what, err)
}
if i < 0 {
return 0, 0, fmt.Errorf("negative values are not allowed for %s, found %d", what, i)
}
return i, start, nil
}
}
return 0, 0, fmt.Errorf("couldn't find a %s before %s", string(sep), what)
}
type blocksByStart []ProfileBlock
func (b blocksByStart) Len() int { return len(b) }
func (b blocksByStart) Swap(i, j int) { b[i], b[j] = b[j], b[i] }
func (b blocksByStart) Less(i, j int) bool {
bi, bj := b[i], b[j]
return bi.StartLine < bj.StartLine || bi.StartLine == bj.StartLine && bi.StartCol < bj.StartCol
}
// Boundary represents the position in a source file of the beginning or end of a
// block as reported by the coverage profile. In HTML mode, it will correspond to
// the opening or closing of a tag and will be used to colorize the source
type Boundary struct {
Offset int // Location as a byte offset in the source file.
Start bool // Is this the start of a block?
Count int // Event count from the cover profile.
Norm float64 // Count normalized to [0..1].
Index int // Order in input file.
}
// Boundaries returns a Profile as a set of Boundary objects within the provided src.
func (p *Profile) Boundaries(src []byte) (boundaries []Boundary) {
// Find maximum count.
max := 0
for _, b := range p.Blocks {
if b.Count > max {
max = b.Count
}
}
// Divisor for normalization.
divisor := math.Log(float64(max))
// boundary returns a Boundary, populating the Norm field with a normalized Count.
index := 0
boundary := func(offset int, start bool, count int) Boundary {
b := Boundary{Offset: offset, Start: start, Count: count, Index: index}
index++
if !start || count == 0 {
return b
}
if max <= 1 {
b.Norm = 0.8 // Profile is in"set" mode; we want a heat map. Use cov8 in the CSS.
} else if count > 0 {
b.Norm = math.Log(float64(count)) / divisor
}
return b
}
line, col := 1, 2 // TODO: Why is this 2?
for si, bi := 0, 0; si < len(src) && bi < len(p.Blocks); {
b := p.Blocks[bi]
if b.StartLine == line && b.StartCol == col {
boundaries = append(boundaries, boundary(si, true, b.Count))
}
if b.EndLine == line && b.EndCol == col || line > b.EndLine {
boundaries = append(boundaries, boundary(si, false, 0))
bi++
continue // Don't advance through src; maybe the next block starts here.
}
if src[si] == '\n' {
line++
col = 0
}
col++
si++
}
sort.Sort(boundariesByPos(boundaries))
return
}
type boundariesByPos []Boundary
func (b boundariesByPos) Len() int { return len(b) }
func (b boundariesByPos) Swap(i, j int) { b[i], b[j] = b[j], b[i] }
func (b boundariesByPos) Less(i, j int) bool {
if b[i].Offset == b[j].Offset {
// Boundaries at the same offset should be ordered according to
// their original position.
return b[i].Index < b[j].Index
}
return b[i].Offset < b[j].Offset
}
================================================
FILE: cover/profile_test.go
================================================
// Copyright 2019 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package cover
import (
"fmt"
"os"
"path/filepath"
"reflect"
"testing"
)
func TestParseProfiles(t *testing.T) {
tests := []struct {
name string
input string
output []*Profile
expectErr bool
}{
{
name: "parsing an empty file produces empty output",
input: `mode: set`,
output: []*Profile{},
},
{
name: "simple valid file produces expected output",
input: `mode: set
some/fancy/path:42.69,44.16 2 1`,
output: []*Profile{
{
FileName: "some/fancy/path",
Mode: "set",
Blocks: []ProfileBlock{
{
StartLine: 42, StartCol: 69,
EndLine: 44, EndCol: 16,
NumStmt: 2, Count: 1,
},
},
},
},
},
{
name: "file with syntax characters in path produces expected output",
input: `mode: set
some fancy:path/some,file.go:42.69,44.16 2 1`,
output: []*Profile{
{
FileName: "some fancy:path/some,file.go",
Mode: "set",
Blocks: []ProfileBlock{
{
StartLine: 42, StartCol: 69,
EndLine: 44, EndCol: 16,
NumStmt: 2, Count: 1,
},
},
},
},
},
{
name: "file with multiple blocks in one file produces expected output",
input: `mode: set
some/fancy/path:42.69,44.16 2 1
some/fancy/path:44.16,46.3 1 0`,
output: []*Profile{
{
FileName: "some/fancy/path",
Mode: "set",
Blocks: []ProfileBlock{
{
StartLine: 42, StartCol: 69,
EndLine: 44, EndCol: 16,
NumStmt: 2, Count: 1,
},
{
StartLine: 44, StartCol: 16,
EndLine: 46, EndCol: 3,
NumStmt: 1, Count: 0,
},
},
},
},
},
{
name: "file with multiple files produces expected output",
input: `mode: set
another/fancy/path:44.16,46.3 1 0
some/fancy/path:42.69,44.16 2 1`,
output: []*Profile{
{
FileName: "another/fancy/path",
Mode: "set",
Blocks: []ProfileBlock{
{
StartLine: 44, StartCol: 16,
EndLine: 46, EndCol: 3,
NumStmt: 1, Count: 0,
},
},
},
{
FileName: "some/fancy/path",
Mode: "set",
Blocks: []ProfileBlock{
{
StartLine: 42, StartCol: 69,
EndLine: 44, EndCol: 16,
NumStmt: 2, Count: 1,
},
},
},
},
},
{
name: "intertwined files are merged correctly",
input: `mode: set
some/fancy/path:42.69,44.16 2 1
another/fancy/path:47.2,47.13 1 1
some/fancy/path:44.16,46.3 1 0`,
output: []*Profile{
{
FileName: "another/fancy/path",
Mode: "set",
Blocks: []ProfileBlock{
{
StartLine: 47, StartCol: 2,
EndLine: 47, EndCol: 13,
NumStmt: 1, Count: 1,
},
},
},
{
FileName: "some/fancy/path",
Mode: "set",
Blocks: []ProfileBlock{
{
StartLine: 42, StartCol: 69,
EndLine: 44, EndCol: 16,
NumStmt: 2, Count: 1,
},
{
StartLine: 44, StartCol: 16,
EndLine: 46, EndCol: 3,
NumStmt: 1, Count: 0,
},
},
},
},
},
{
name: "duplicate blocks are merged correctly",
input: `mode: count
some/fancy/path:42.69,44.16 2 4
some/fancy/path:42.69,44.16 2 3`,
output: []*Profile{
{
FileName: "some/fancy/path",
Mode: "count",
Blocks: []ProfileBlock{
{
StartLine: 42, StartCol: 69,
EndLine: 44, EndCol: 16,
NumStmt: 2, Count: 7,
},
},
},
},
},
{
name: "an invalid mode line is an error",
input: `mode:count`,
expectErr: true,
},
{
name: "a missing field is an error",
input: `mode: count
some/fancy/path:42.69,44.16 2`,
expectErr: true,
},
{
name: "a missing path field is an error",
input: `mode: count
42.69,44.16 2 3`,
expectErr: true,
},
{
name: "a non-numeric count is an error",
input: `mode: count
42.69,44.16 2 nope`,
expectErr: true,
},
{
name: "an empty path is an error",
input: `mode: count
:42.69,44.16 2 3`,
expectErr: true,
},
{
name: "a negative count is an error",
input: `mode: count
some/fancy/path:42.69,44.16 2 -1`,
expectErr: true,
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
fname := filepath.Join(t.TempDir(), "test.cov")
if err := os.WriteFile(fname, []byte(tc.input), 0644); err != nil {
t.Fatal(err)
}
result, err := ParseProfiles(fname)
if err != nil {
if !tc.expectErr {
t.Errorf("Unexpected error: %v", err)
}
return
}
if tc.expectErr {
t.Errorf("Expected an error, but got value %q", stringifyProfileArray(result))
}
if !reflect.DeepEqual(result, tc.output) {
t.Errorf("Mismatched results.\nExpected: %s\nActual: %s", stringifyProfileArray(tc.output), stringifyProfileArray(result))
}
})
}
}
func stringifyProfileArray(profiles []*Profile) string {
deref := make([]Profile, 0, len(profiles))
for _, p := range profiles {
deref = append(deref, *p)
}
return fmt.Sprintf("%#v", deref)
}
func BenchmarkParseLine(b *testing.B) {
const line = "k8s.io/kubernetes/cmd/kube-controller-manager/app/options/ttlafterfinishedcontroller.go:31.73,32.14 1 1"
b.SetBytes(int64(len(line)))
for b.Loop() {
parseLine(line)
}
}
================================================
FILE: go/analysis/analysis.go
================================================
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package analysis
import (
"flag"
"fmt"
"go/ast"
"go/token"
"go/types"
"reflect"
"time"
)
// An Analyzer describes an analysis function and its options.
type Analyzer struct {
// The Name of the analyzer must be a valid Go identifier
// as it may appear in command-line flags, URLs, and so on.
Name string
// Doc is the documentation for the analyzer.
// The part before the first "\n\n" is the title
// (no capital or period, max ~60 letters).
Doc string
// URL holds an optional link to a web page with additional
// documentation for this analyzer.
URL string
// Flags defines any flags accepted by the analyzer.
// The manner in which these flags are exposed to the user
// depends on the driver which runs the analyzer.
Flags flag.FlagSet
// Run applies the analyzer to a package.
// It returns an error if the analyzer failed.
//
// On success, the Run function may return a result
// computed by the Analyzer; its type must match ResultType.
// The driver makes this result available as an input to
// another Analyzer that depends directly on this one (see
// Requires) when it analyzes the same package.
//
// To pass analysis results between packages (and thus
// potentially between address spaces), use Facts, which are
// serializable.
Run func(*Pass) (any, error)
// RunDespiteErrors allows the driver to invoke
// the Run method of this analyzer even on a
// package that contains parse or type errors.
// The [Pass.TypeErrors] field may consequently be non-empty.
RunDespiteErrors bool
// Requires is a set of analyzers that must run successfully
// before this one on a given package. This analyzer may inspect
// the outputs produced by each analyzer in Requires.
// The graph over analyzers implied by Requires edges must be acyclic.
//
// Requires establishes a "horizontal" dependency between
// analysis passes (different analyzers, same package).
Requires []*Analyzer
// ResultType is the type of the optional result of the Run function.
ResultType reflect.Type
// FactTypes indicates that this analyzer imports and exports
// Facts of the specified concrete types.
// An analyzer that uses facts may assume that its import
// dependencies have been similarly analyzed before it runs.
// Facts must be pointers.
//
// FactTypes establishes a "vertical" dependency between
// analysis passes (same analyzer, different packages).
FactTypes []Fact
}
func (a *Analyzer) String() string { return a.Name }
// A Pass provides information to the Run function that
// applies a specific analyzer to a single Go package.
//
// It forms the interface between the analysis logic and the driver
// program, and has both input and an output components.
//
// As in a compiler, one pass may depend on the result computed by another.
//
// The Run function should not call any of the Pass functions concurrently.
type Pass struct {
Analyzer *Analyzer // the identity of the current analyzer
// syntax and type information
Fset *token.FileSet // file position information; Run may add new files
Files []*ast.File // the abstract syntax tree of each file
OtherFiles []string // names of non-Go files of this package
IgnoredFiles []string // names of ignored source files in this package
Pkg *types.Package // type information about the package
TypesInfo *types.Info // type information about the syntax trees
TypesSizes types.Sizes // function for computing sizes of types
TypeErrors []types.Error // type errors (only if Analyzer.RunDespiteErrors)
Module *Module // the package's enclosing module (possibly nil in some drivers)
// Report reports a Diagnostic, a finding about a specific location
// in the analyzed source code such as a potential mistake.
// It may be called by the Run function.
Report func(Diagnostic)
// ResultOf provides the inputs to this analysis pass, which are
// the corresponding results of its prerequisite analyzers.
// The map keys are the elements of Analysis.Required,
// and the type of each corresponding value is the required
// analysis's ResultType.
ResultOf map[*Analyzer]any
// ReadFile returns the contents of the named file.
//
// The only valid file names are the elements of OtherFiles
// and IgnoredFiles, and names returned by
// Fset.File(f.FileStart).Name() for each f in Files.
//
// Analyzers must use this function (if provided) instead of
// accessing the file system directly. This allows a driver to
// provide a virtualized file tree (including, for example,
// unsaved editor buffers) and to track dependencies precisely
// to avoid unnecessary recomputation.
ReadFile func(filename string) ([]byte, error)
// -- facts --
// ImportObjectFact retrieves a fact associated with obj.
// Given a value ptr of type *T, where *T satisfies Fact,
// ImportObjectFact copies the value to *ptr.
//
// ImportObjectFact panics if called after the pass is complete.
// ImportObjectFact is not concurrency-safe.
ImportObjectFact func(obj types.Object, fact Fact) bool
// ImportPackageFact retrieves a fact associated with package pkg,
// which must be this package or one of its dependencies.
// See comments for ImportObjectFact.
ImportPackageFact func(pkg *types.Package, fact Fact) bool
// ExportObjectFact associates a fact of type *T with the obj,
// replacing any previous fact of that type.
//
// ExportObjectFact panics if it is called after the pass is
// complete, or if obj does not belong to the package being analyzed.
// ExportObjectFact is not concurrency-safe.
ExportObjectFact func(obj types.Object, fact Fact)
// ExportPackageFact associates a fact with the current package.
// See comments for ExportObjectFact.
ExportPackageFact func(fact Fact)
// AllPackageFacts returns a new slice containing all package
// facts of the analysis's FactTypes in unspecified order.
// See comments for AllObjectFacts.
AllPackageFacts func() []PackageFact
// AllObjectFacts returns a new slice containing all object
// facts of the analysis's FactTypes in unspecified order.
//
// The result includes all facts exported by packages
// whose symbols are referenced by the current package
// (by qualified identifiers or field/method selections).
// And it includes all facts exported from the current
// package by the current analysis pass.
AllObjectFacts func() []ObjectFact
/* Further fields may be added in future. */
}
// PackageFact is a package together with an associated fact.
type PackageFact struct {
Package *types.Package
Fact Fact
}
// ObjectFact is an object together with an associated fact.
type ObjectFact struct {
Object types.Object
Fact Fact
}
// Reportf is a helper function that reports a Diagnostic using the
// specified position and formatted error message.
func (pass *Pass) Reportf(pos token.Pos, format string, args ...any) {
msg := fmt.Sprintf(format, args...)
pass.Report(Diagnostic{Pos: pos, Message: msg})
}
// The Range interface provides a range. It's equivalent to and satisfied by
// ast.Node.
type Range interface {
Pos() token.Pos // position of first character belonging to the node
End() token.Pos // position of first character immediately after the node
}
// ReportRangef is a helper function that reports a Diagnostic using the
// range provided. ast.Node values can be passed in as the range because
// they satisfy the Range interface.
func (pass *Pass) ReportRangef(rng Range, format string, args ...any) {
msg := fmt.Sprintf(format, args...)
pass.Report(Diagnostic{Pos: rng.Pos(), End: rng.End(), Message: msg})
}
func (pass *Pass) String() string {
return fmt.Sprintf("%s@%s", pass.Analyzer.Name, pass.Pkg.Path())
}
// A Fact is an intermediate fact produced during analysis.
//
// Each fact is associated with a named declaration (a types.Object) or
// with a package as a whole. A single object or package may have
// multiple associated facts, but only one of any particular fact type.
//
// A Fact represents a predicate such as "never returns", but does not
// represent the subject of the predicate such as "function F" or "package P".
//
// Facts may be produced in one analysis pass and consumed by another
// analysis pass even if these are in different address spaces.
// If package P imports Q, all facts about Q produced during
// analysis of that package will be available during later analysis of P.
// Facts are analogous to type export data in a build system:
// just as export data enables separate compilation of several passes,
// facts enable "separate analysis".
//
// Each pass (a, p) starts with the set of facts produced by the
// same analyzer a applied to the packages directly imported by p.
// The analysis may add facts to the set, and they may be exported in turn.
// An analysis's Run function may retrieve facts by calling
// Pass.Import{Object,Package}Fact and update them using
// Pass.Export{Object,Package}Fact.
//
// A fact is logically private to its Analysis. To pass values
// between different analyzers, use the results mechanism;
// see Analyzer.Requires, Analyzer.ResultType, and Pass.ResultOf.
//
// A Fact type must be a pointer.
// Facts are encoded and decoded using encoding/gob.
// A Fact may implement the GobEncoder/GobDecoder interfaces
// to customize its encoding. Fact encoding should not fail.
//
// A Fact should not be modified once exported.
type Fact interface {
AFact() // dummy method to avoid type errors
}
// A Module describes the module to which a package belongs.
type Module struct {
Path string // module path
Version string // module version ("" if unknown, such as for workspace modules)
Replace *Module // replaced by this module
Time *time.Time // time version was created
Main bool // is this the main module?
Indirect bool // is this module only an indirect dependency of main module?
Dir string // directory holding files for this module, if any
GoMod string // path to go.mod file used when loading this module, if any
GoVersion string // go version used in module (e.g. "go1.22.0")
Error *ModuleError // error loading module
}
// ModuleError holds errors loading a module.
type ModuleError struct {
Err string // the error itself
}
================================================
FILE: go/analysis/analysistest/analysistest.go
================================================
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package analysistest provides utilities for testing analyzers.
package analysistest
import (
"bytes"
"fmt"
"go/ast"
"go/format"
"go/token"
"go/types"
"log"
"maps"
"os"
"path/filepath"
"regexp"
"runtime"
"slices"
"sort"
"strconv"
"strings"
"testing"
"text/scanner"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/checker"
"golang.org/x/tools/go/analysis/internal"
"golang.org/x/tools/go/packages"
"golang.org/x/tools/internal/analysis/driverutil"
"golang.org/x/tools/internal/diff"
"golang.org/x/tools/internal/testenv"
"golang.org/x/tools/txtar"
)
// WriteFiles is a helper function that creates a temporary directory
// and populates it with a GOPATH-style project using filemap (which
// maps file names to contents). On success it returns the name of the
// directory and a cleanup function to delete it.
//
// TODO(adonovan): provide a newer version that accepts a testing.T,
// calls T.TempDir, and calls T.Fatal on any error, avoiding the need
// to return cleanup or err:
//
// func WriteFilesToTmp(t *testing.T filemap map[string]string) string
func WriteFiles(filemap map[string]string) (dir string, cleanup func(), err error) {
gopath, err := os.MkdirTemp("", "analysistest")
if err != nil {
return "", nil, err
}
cleanup = func() { os.RemoveAll(gopath) }
for name, content := range filemap {
filename := filepath.Join(gopath, "src", name)
os.MkdirAll(filepath.Dir(filename), 0777) // ignore error
if err := os.WriteFile(filename, []byte(content), 0666); err != nil {
cleanup()
return "", nil, err
}
}
return gopath, cleanup, nil
}
// TestData returns the effective filename of
// the program's "testdata" directory.
// This function may be overridden by projects using
// an alternative build system (such as Blaze) that
// does not run a test in its package directory.
var TestData = func() string {
testdata, err := filepath.Abs("testdata")
if err != nil {
log.Fatal(err)
}
return testdata
}
// Testing is an abstraction of a *testing.T.
type Testing interface {
Errorf(format string, args ...any)
}
// RunWithSuggestedFixes behaves like Run, but additionally applies
// suggested fixes and verifies their output.
//
// It uses golden files, placed alongside each source file, to express
// the desired output: the expected transformation of file example.go
// is specified in file example.go.golden.
//
// Golden files may be of two forms: a plain Go source file, or a
// txtar archive.
//
// A plain Go source file indicates the expected result of applying
// all suggested fixes to the original file.
//
// A txtar archive specifies, in each section, the expected result of
// applying all suggested fixes of a given message to the original
// file; the name of the archive section is the fix's message. In this
// way, the various alternative fixes offered by a single diagnostic
// can be tested independently. Here's an example:
//
// -- turn into single negation --
// package pkg
//
// func fn(b1, b2 bool) {
// if !b1 { // want `negating a boolean twice`
// println()
// }
// }
//
// -- remove double negation --
// package pkg
//
// func fn(b1, b2 bool) {
// if b1 { // want `negating a boolean twice`
// println()
// }
// }
//
// # Conflicts
//
// Regardless of the form of the golden file, it is possible for
// multiple fixes to conflict, either because they overlap, or are
// close enough together that the particular diff algorithm cannot
// separate them.
//
// RunWithSuggestedFixes uses a simple three-way merge to accumulate
// fixes, similar to a git merge. The merge algorithm may be able to
// coalesce identical edits, for example duplicate imports of the same
// package. (Bear in mind that this is an editorial decision. In
// general, coalescing identical edits may not be correct: consider
// two statements that increment the same counter.)
//
// If there are conflicts, the test fails. In any case, the
// non-conflicting edits will be compared against the expected output.
// In this situation, we recommend that you increase the textual
// separation between conflicting parts or, if that fails, split
// your tests into smaller parts.
//
// If a diagnostic offers multiple fixes for the same problem, they
// are almost certain to conflict, so in this case you should define
// the expected output using a multi-section txtar file as described
// above.
func RunWithSuggestedFixes(t Testing, dir string, a *analysis.Analyzer, patterns ...string) []*Result {
results := Run(t, dir, a, patterns...)
// If the immediate caller of RunWithSuggestedFixes is in
// x/tools, we apply stricter checks as required by gopls.
inTools := false
{
var pcs [1]uintptr
n := runtime.Callers(1, pcs[:])
frames := runtime.CallersFrames(pcs[:n])
fr, _ := frames.Next()
if fr.Func != nil && strings.HasPrefix(fr.Func.Name(), "golang.org/x/tools/") {
inTools = true
}
}
generated := make(map[*token.File]bool)
// Process each result (package) separately, matching up the suggested
// fixes into a diff, which we will compare to the .golden file. We have
// to do this per-result in case a file appears in two packages, such as in
// packages with tests, where mypkg/a.go will appear in both mypkg and
// mypkg.test. In that case, the analyzer may suggest the same set of
// changes to a.go for each package. If we merge all the results, those
// changes get doubly applied, which will cause conflicts or mismatches.
// Validating the results separately means as long as the two analyses
// don't produce conflicting suggestions for a single file, everything
// should match up.
for _, result := range results {
act := result.Action
// Compute set of generated files.
for _, file := range internal.ActionPass(act).Files {
// Memoize, since there may be many actions
// for the same package (list of files).
tokFile := act.Package.Fset.File(file.Pos())
if _, seen := generated[tokFile]; !seen {
generated[tokFile] = ast.IsGenerated(file)
}
}
// For each fix, split its edits by file and convert to diff form.
var (
// fixEdits: message -> fixes -> filename -> edits
//
// TODO(adonovan): this mapping assumes fix.Messages
// are unique across analyzers, whereas they are only
// unique within a given Diagnostic.
fixEdits = make(map[string][]map[string][]diff.Edit)
allFilenames = make(map[string]bool)
)
for _, diag := range act.Diagnostics {
// Fixes are validated upon creation in Pass.Report.
fixloop:
for _, fix := range diag.SuggestedFixes {
// Assert that lazy fixes have a Category (#65578, #65087).
if inTools && len(fix.TextEdits) == 0 && diag.Category == "" {
t.Errorf("missing Diagnostic.Category for SuggestedFix without TextEdits (gopls requires the category for the name of the fix command")
}
// Skip any fix that edits a generated file.
for _, edit := range fix.TextEdits {
file := act.Package.Fset.File(edit.Pos)
if generated[file] {
continue fixloop
}
}
// Convert edits to diff form.
// Group fixes by message and file.
edits := make(map[string][]diff.Edit)
for _, edit := range fix.TextEdits {
file := act.Package.Fset.File(edit.Pos)
allFilenames[file.Name()] = true
edits[file.Name()] = append(edits[file.Name()], diff.Edit{
Start: file.Offset(edit.Pos),
End: file.Offset(edit.End),
New: string(edit.NewText),
})
}
fixEdits[fix.Message] = append(fixEdits[fix.Message], edits)
}
}
merge := func(file, message string, x, y []diff.Edit) []diff.Edit {
z, ok := diff.Merge(x, y)
if !ok {
t.Errorf("in file %s, conflict applying fix %q", file, message)
return x // discard y
}
return z
}
// Because the checking is driven by original
// filenames, there is no way to express that a fix
// (e.g. extract declaration) creates a new file.
for _, filename := range slices.Sorted(maps.Keys(allFilenames)) {
// Read the original file.
content, err := os.ReadFile(filename)
if err != nil {
t.Errorf("error reading %s: %v", filename, err)
continue
}
// check checks that the accumulated edits applied
// to the original content yield the wanted content.
check := func(prefix string, accumulated []diff.Edit, want []byte) {
if err := applyDiffsAndCompare(result.Pass.Pkg, filename, content, want, accumulated); err != nil {
t.Errorf("%s: %s", prefix, err)
}
}
// Read the golden file. It may have one of two forms:
// (1) A txtar archive with one section per fix title,
// including all fixes of just that title.
// (2) The expected output for file.Name after all (?) fixes are applied.
// This form requires that no diagnostic has multiple fixes.
ar, err := txtar.ParseFile(filename + ".golden")
if err != nil {
t.Errorf("error reading %s.golden: %v", filename, err)
continue
}
if len(ar.Files) > 0 {
// Form #1: one archive section per kind of suggested fix.
if len(ar.Comment) > 0 {
// Disallow the combination of comment and archive sections.
t.Errorf("%s.golden has leading comment; we don't know what to do with it", filename)
continue
}
// Each archive section is named for a fix.Message.
// Accumulate the parts of the fix that apply to the current file,
// using a simple three-way merge, discarding conflicts,
// then apply the merged edits and compare to the archive section.
for _, section := range ar.Files {
message, want := section.Name, section.Data
var accumulated []diff.Edit
for _, fix := range fixEdits[message] {
accumulated = merge(filename, message, accumulated, fix[filename])
}
check(fmt.Sprintf("all fixes of message %q", message), accumulated, want)
}
} else {
// Form #2: all suggested fixes are represented by a single file.
want := ar.Comment
var accumulated []diff.Edit
for _, message := range slices.Sorted(maps.Keys(fixEdits)) {
for _, fix := range fixEdits[message] {
accumulated = merge(filename, message, accumulated, fix[filename])
}
}
check("all fixes", accumulated, want)
}
}
}
return results
}
// applyDiffsAndCompare applies edits to original and compares the results against
// want after formatting both. fileName is use solely for error reporting.
func applyDiffsAndCompare(pkg *types.Package, filename string, original, want []byte, edits []diff.Edit) error {
// Relativize filename, for tidier errors.
if cwd, err := os.Getwd(); err == nil {
if rel, err := filepath.Rel(cwd, filename); err == nil {
filename = rel
}
}
if len(edits) == 0 {
return fmt.Errorf("%s: no edits", filename)
}
fixedBytes, err := diff.ApplyBytes(original, edits)
if err != nil {
return fmt.Errorf("%s: error applying fixes: %v (see possible explanations at RunWithSuggestedFixes)", filename, err)
}
fixed, err := driverutil.FormatSourceRemoveImports(pkg, fixedBytes)
if err != nil {
return fmt.Errorf("%s: error formatting resulting source: %v\n%s", filename, err, fixedBytes)
}
want, err = format.Source(want)
if err != nil {
return fmt.Errorf("%s.golden: error formatting golden file: %v\n%s", filename, err, fixed)
}
// Keep error reporting logic below consistent with
// TestScript in ../internal/checker/fix_test.go!
unified := func(xlabel, ylabel string, x, y []byte) string {
x = append(slices.Clip(bytes.TrimSpace(x)), '\n')
y = append(slices.Clip(bytes.TrimSpace(y)), '\n')
return diff.Unified(xlabel, ylabel, string(x), string(y))
}
if diff := unified(filename+" (fixed)", filename+" (want)", fixed, want); diff != "" {
return fmt.Errorf("unexpected %s content:\n"+
"-- original --\n%s\n"+
"-- fixed --\n%s\n"+
"-- want --\n%s\n"+
"-- diff original fixed --\n%s\n"+
"-- diff fixed want --\n%s",
filename,
original,
fixed,
want,
unified(filename+" (original)", filename+" (fixed)", original, fixed),
diff)
}
return nil
}
// Run applies an analysis to the packages denoted by the "go list" patterns.
//
// It loads the packages from the specified
// directory using golang.org/x/tools/go/packages, runs the analysis on
// them, and checks that each analysis emits the expected diagnostics
// and facts specified by the contents of '// want ...' comments in the
// package's source files. It treats a comment of the form
// "//...// want..." or "/*...// want... */" as if it starts at 'want'.
//
// If the directory contains a go.mod file, Run treats it as the root of the
// Go module in which to work. Otherwise, Run treats it as the root of a
// GOPATH-style tree, with package contained in the src subdirectory.
//
// An expectation of a Diagnostic is specified by a string literal
// containing a regular expression that must match the diagnostic
// message. For example:
//
// fmt.Printf("%s", 1) // want `cannot provide int 1 to %s`
//
// An expectation of a Fact associated with an object is specified by
// 'name:"pattern"', where name is the name of the object, which must be
// declared on the same line as the comment, and pattern is a regular
// expression that must match the string representation of the fact,
// fmt.Sprint(fact). For example:
//
// func panicf(format string, args interface{}) { // want panicf:"printfWrapper"
//
// Package facts are specified by the name "package" and appear on
// line 1 of the first source file of the package.
//
// A single 'want' comment may contain a mixture of diagnostic and fact
// expectations, including multiple facts about the same object:
//
// // want "diag" "diag2" x:"fact1" x:"fact2" y:"fact3"
//
// Unexpected diagnostics and facts, and unmatched expectations, are
// reported as errors to the Testing.
//
// Run reports an error to the Testing if loading or analysis failed.
// Run also returns a Result for each package for which analysis was
// attempted, even if unsuccessful. It is safe for a test to ignore all
// the results, but a test may use it to perform additional checks.
func Run(t Testing, dir string, a *analysis.Analyzer, patterns ...string) []*Result {
if t, ok := t.(testing.TB); ok {
testenv.NeedsGoPackages(t)
}
pkgs, err := loadPackages(dir, patterns...)
if err != nil {
t.Errorf("loading %s: %v", patterns, err)
return nil
}
// Print parse and type errors to the test log.
// (Do not print them to stderr, which would pollute
// the log in cases where the tests pass.)
if t, ok := t.(testing.TB); ok && !a.RunDespiteErrors {
packages.Visit(pkgs, nil, func(pkg *packages.Package) {
for _, err := range pkg.Errors {
t.Log(err)
}
})
}
res, err := checker.Analyze([]*analysis.Analyzer{a}, pkgs, nil)
if err != nil {
t.Errorf("Analyze: %v", err)
return nil
}
var results []*Result
for _, act := range res.Roots {
if act.Err != nil {
t.Errorf("error analyzing %s: %v", act, act.Err)
} else {
check(t, dir, act)
}
// Compute legacy map of facts relating to this package.
facts := make(map[types.Object][]analysis.Fact)
for _, objFact := range act.AllObjectFacts() {
if obj := objFact.Object; obj.Pkg() == act.Package.Types {
facts[obj] = append(facts[obj], objFact.Fact)
}
}
for _, pkgFact := range act.AllPackageFacts() {
if pkgFact.Package == act.Package.Types {
facts[nil] = append(facts[nil], pkgFact.Fact)
}
}
// Construct the legacy result.
results = append(results, &Result{
Pass: internal.ActionPass(act), // may be nil
Diagnostics: act.Diagnostics,
Facts: facts,
Result: act.Result,
Err: act.Err,
Action: act,
})
}
return results
}
// A Result holds the result of applying an analyzer to a package.
//
// Facts contains only facts associated with the package and its objects.
//
// This internal type was inadvertently and regrettably exposed
// through a public type alias. It is essentially redundant with
// [checker.Action], but must be retained for compatibility. Clients may
// access the public fields of the Pass but must not invoke any of
// its "verbs", since the pass is already complete.
type Result struct {
Action *checker.Action
// legacy fields (do not use)
Facts map[types.Object][]analysis.Fact // nil key => package fact
Pass *analysis.Pass // nil => action not executed
Diagnostics []analysis.Diagnostic // see Action.Diagnostics
Result any // see Action.Result
Err error // see Action.Err
}
// loadPackages uses go/packages to load a specified packages (from source, with
// dependencies) from dir, which is the root of a GOPATH-style project tree.
// loadPackages returns an error if any package had an error, or the pattern
// matched no packages.
func loadPackages(dir string, patterns ...string) ([]*packages.Package, error) {
env := []string{"GOPATH=" + dir, "GO111MODULE=off", "GOWORK=off"} // GOPATH mode
// Undocumented module mode. Will be replaced by something better.
if _, err := os.Stat(filepath.Join(dir, "go.mod")); err == nil {
gowork := filepath.Join(dir, "go.work")
if _, err := os.Stat(gowork); err != nil {
gowork = "off"
}
env = []string{"GO111MODULE=on", "GOPROXY=off", "GOWORK=" + gowork} // module mode
}
// packages.Load loads the real standard library, not a minimal
// fake version, which would be more efficient, especially if we
// have many small tests that import, say, net/http.
// However there is no easy way to make go/packages to consume
// a list of packages we generate and then do the parsing and
// typechecking, though this feature seems to be a recurring need.
mode := packages.NeedName | packages.NeedFiles | packages.NeedCompiledGoFiles | packages.NeedImports |
packages.NeedTypes | packages.NeedTypesSizes | packages.NeedSyntax | packages.NeedTypesInfo |
packages.NeedDeps | packages.NeedModule
cfg := &packages.Config{
Mode: mode,
Dir: dir,
Tests: true,
Env: append(os.Environ(), env...),
}
pkgs, err := packages.Load(cfg, patterns...)
if err != nil {
return nil, err
}
// If any named package couldn't be loaded at all
// (e.g. the Name field is unset), fail fast.
for _, pkg := range pkgs {
if pkg.Name == "" {
return nil, fmt.Errorf("failed to load %q: Errors=%v",
pkg.PkgPath, pkg.Errors)
}
}
if len(pkgs) == 0 {
return nil, fmt.Errorf("no packages matched %s", patterns)
}
return pkgs, nil
}
// check inspects an analysis pass on which the analysis has already
// been run, and verifies that all reported diagnostics and facts match
// specified by the contents of "// want ..." comments in the package's
// source files, which must have been parsed with comments enabled.
func check(t Testing, gopath string, act *checker.Action) {
type key struct {
file string
line int
}
want := make(map[key][]expectation)
// processComment parses expectations out of comments.
processComment := func(filename string, linenum int, text string) {
text = strings.TrimSpace(text)
// Any comment starting with "want" is treated
// as an expectation, even without following whitespace.
if rest, ok := strings.CutPrefix(text, "want"); ok {
lineDelta, expects, err := parseExpectations(rest)
if err != nil {
t.Errorf("%s:%d: in 'want' comment: %s", filename, linenum, err)
return
}
if expects != nil {
want[key{filename, linenum + lineDelta}] = expects
}
}
}
// Extract 'want' comments from parsed Go files.
for _, f := range act.Package.Syntax {
for _, cgroup := range f.Comments {
for _, c := range cgroup.List {
text := strings.TrimPrefix(c.Text, "//")
if text == c.Text { // not a //-comment.
text = strings.TrimPrefix(text, "/*")
text = strings.TrimSuffix(text, "*/")
}
// Hack: treat a comment of the form "//...// want..."
// or "/*...// want... */
// as if it starts at 'want'.
// This allows us to add comments on comments,
// as required when testing the buildtag analyzer.
if i := strings.Index(text, "// want"); i >= 0 {
text = text[i+len("// "):]
}
// It's tempting to compute the filename
// once outside the loop, but it's
// incorrect because it can change due
// to //line directives.
posn := act.Package.Fset.Position(c.Pos())
filename := sanitize(gopath, posn.Filename)
processComment(filename, posn.Line, text)
}
}
}
// Extract 'want' comments from non-Go files.
// TODO(adonovan): we may need to handle //line directives.
files := act.Package.OtherFiles
// Hack: these analyzers need to extract expectations from
// all configurations, so include the files are usually
// ignored. (This was previously a hack in the respective
// analyzers' tests.)
switch act.Analyzer.Name {
case "buildtag", "directive", "plusbuild":
files = slices.Concat(files, act.Package.IgnoredFiles)
}
for _, filename := range files {
data, err := os.ReadFile(filename)
if err != nil {
t.Errorf("can't read '// want' comments from %s: %v", filename, err)
continue
}
filename := sanitize(gopath, filename)
linenum := 0
for line := range strings.SplitSeq(string(data), "\n") {
linenum++
// Hack: treat a comment of the form "//...// want..."
// or "/*...// want... */
// as if it starts at 'want'.
// This allows us to add comments on comments,
// as required when testing the buildtag analyzer.
if i := strings.Index(line, "// want"); i >= 0 {
line = line[i:]
}
if i := strings.Index(line, "//"); i >= 0 {
line = line[i+len("//"):]
processComment(filename, linenum, line)
}
}
}
checkMessage := func(posn token.Position, kind, name, message string) {
posn.Filename = sanitize(gopath, posn.Filename)
k := key{posn.Filename, posn.Line}
expects := want[k]
var unmatched []string
for i, exp := range expects {
if exp.kind == kind && exp.name == name {
if exp.rx.MatchString(message) {
// matched: remove the expectation.
expects[i] = expects[len(expects)-1]
expects = expects[:len(expects)-1]
want[k] = expects
return
}
unmatched = append(unmatched, fmt.Sprintf("%#q", exp.rx))
}
}
if unmatched == nil {
t.Errorf("%v: unexpected %s: %v", posn, kind, message)
} else {
t.Errorf("%v: %s %q does not match pattern %s",
posn, kind, message, strings.Join(unmatched, " or "))
}
}
// Check the diagnostics match expectations.
for _, f := range act.Diagnostics {
// TODO(matloob): Support ranges in analysistest.
posn := act.Package.Fset.Position(f.Pos)
checkMessage(posn, "diagnostic", "", f.Message)
}
// Check the facts match expectations.
// We check only facts relating to the current package.
//
// We report errors in lexical order for determinism.
// (It's only deterministic within each file, not across files,
// because go/packages does not guarantee file.Pos is ascending
// across the files of a single compilation unit.)
// package facts: reported at start of first file
for _, pkgFact := range act.AllPackageFacts() {
if pkgFact.Package == act.Package.Types {
posn := act.Package.Fset.Position(act.Package.Syntax[0].Pos())
posn.Line, posn.Column = 1, 1
checkMessage(posn, "fact", "package", fmt.Sprint(pkgFact))
}
}
// object facts: reported at line of object declaration
objFacts := act.AllObjectFacts()
sort.Slice(objFacts, func(i, j int) bool {
return objFacts[i].Object.Pos() < objFacts[j].Object.Pos()
})
for _, objFact := range objFacts {
if obj := objFact.Object; obj.Pkg() == act.Package.Types {
posn := act.Package.Fset.Position(obj.Pos())
checkMessage(posn, "fact", obj.Name(), fmt.Sprint(objFact.Fact))
}
}
// Reject surplus expectations.
//
// Sometimes an Analyzer reports two similar diagnostics on a
// line with only one expectation. The reader may be confused by
// the error message.
// TODO(adonovan): print a better error:
// "got 2 diagnostics here; each one needs its own expectation".
var surplus []string
for key, expects := range want {
for _, exp := range expects {
err := fmt.Sprintf("%s:%d: no %s was reported matching %#q", key.file, key.line, exp.kind, exp.rx)
surplus = append(surplus, err)
}
}
sort.Strings(surplus)
for _, err := range surplus {
t.Errorf("%s", err)
}
}
type expectation struct {
kind string // either "fact" or "diagnostic"
name string // name of object to which fact belongs, or "package" ("fact" only)
rx *regexp.Regexp
}
func (ex expectation) String() string {
return fmt.Sprintf("%s %s:%q", ex.kind, ex.name, ex.rx) // for debugging
}
// parseExpectations parses the content of a "// want ..." comment
// and returns the expectations, a mixture of diagnostics ("rx") and
// facts (name:"rx").
func parseExpectations(text string) (lineDelta int, expects []expectation, err error) {
var scanErr string
sc := new(scanner.Scanner).Init(strings.NewReader(text))
sc.Error = func(s *scanner.Scanner, msg string) {
scanErr = msg // e.g. bad string escape
}
sc.Mode = scanner.ScanIdents | scanner.ScanStrings | scanner.ScanRawStrings | scanner.ScanInts
scanRegexp := func(tok rune) (*regexp.Regexp, error) {
if tok != scanner.String && tok != scanner.RawString {
return nil, fmt.Errorf("got %s, want regular expression",
scanner.TokenString(tok))
}
pattern, _ := strconv.Unquote(sc.TokenText()) // can't fail
return regexp.Compile(pattern)
}
for {
tok := sc.Scan()
switch tok {
case '+':
tok = sc.Scan()
if tok != scanner.Int {
return 0, nil, fmt.Errorf("got +%s, want +Int", scanner.TokenString(tok))
}
lineDelta, _ = strconv.Atoi(sc.TokenText())
case scanner.String, scanner.RawString:
rx, err := scanRegexp(tok)
if err != nil {
return 0, nil, err
}
expects = append(expects, expectation{"diagnostic", "", rx})
case scanner.Ident:
name := sc.TokenText()
tok = sc.Scan()
if tok != ':' {
return 0, nil, fmt.Errorf("got %s after %s, want ':'",
scanner.TokenString(tok), name)
}
tok = sc.Scan()
rx, err := scanRegexp(tok)
if err != nil {
return 0, nil, err
}
expects = append(expects, expectation{"fact", name, rx})
case scanner.EOF:
if scanErr != "" {
return 0, nil, fmt.Errorf("%s", scanErr)
}
return lineDelta, expects, nil
default:
return 0, nil, fmt.Errorf("unexpected %s", scanner.TokenString(tok))
}
}
}
// sanitize removes the GOPATH portion of the filename,
// typically a gnarly /tmp directory, and returns the rest.
func sanitize(gopath, filename string) string {
prefix := gopath + string(os.PathSeparator) + "src" + string(os.PathSeparator)
return filepath.ToSlash(strings.TrimPrefix(filename, prefix))
}
================================================
FILE: go/analysis/analysistest/analysistest_test.go
================================================
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package analysistest_test
import (
"fmt"
"go/token"
"log"
"os"
"reflect"
"strings"
"testing"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/analysistest"
"golang.org/x/tools/go/analysis/passes/findcall"
"golang.org/x/tools/internal/testenv"
"golang.org/x/tools/internal/testfiles"
"golang.org/x/tools/txtar"
)
func init() {
// Run() decides when tests use GOPATH mode or modules.
// We turn off GOPROXY just for good measure.
if err := os.Setenv("GOPROXY", "off"); err != nil {
log.Fatal(err)
}
}
// TestTheTest tests the analysistest testing infrastructure.
func TestTheTest(t *testing.T) {
testenv.NeedsTool(t, "go")
// We'll simulate a partly failing test of the findcall analysis,
// which (by default) reports calls to functions named 'println'.
findcall.Analyzer.Flags.Set("name", "println")
filemap := map[string]string{
"a/b.go": `package main // want package:"found"
func main() {
// The expectation is ill-formed:
print() // want: "diagnostic"
print() // want foo"fact"
print() // want foo:
print() // want "\xZZ scan error"
// A diagnostic is reported at this line, but the expectation doesn't match:
println("hello, world") // want "wrong expectation text"
// An unexpected diagnostic is reported at this line:
println() // trigger an unexpected diagnostic
// No diagnostic is reported at this line:
print() // want "unsatisfied expectation"
// OK
println("hello, world") // want "call of println"
// OK /* */-form.
println("안녕, 세계") /* want "call of println" */
// OK (nested comment)
println("Γειά σου, Κόσμε") // some comment // want "call of println"
// OK (nested comment in /**/)
println("你好,世界") /* some comment // want "call of println" */
// OK (multiple expectations on same line)
println(); println() // want "call of println(...)" "call of println(...)"
// A Line that is not formatted correctly in the golden file.
}
// OK (facts and diagnostics on same line)
func println(...interface{}) { println() } // want println:"found" "call of println(...)"
`,
"a/b.go.golden": `package main // want package:"found"
func main() {
// The expectation is ill-formed:
print() // want: "diagnostic"
print() // want foo"fact"
print() // want foo:
print() // want "\xZZ scan error"
// A diagnostic is reported at this line, but the expectation doesn't match:
println_TEST_("hello, world") // want "wrong expectation text"
// An unexpected diagnostic is reported at this line:
println_TEST_() // trigger an unexpected diagnostic
// No diagnostic is reported at this line:
print() // want "unsatisfied expectation"
// OK
println_TEST_("hello, world") // want "call of println"
// OK /* */-form.
println_TEST_("안녕, 세계") /* want "call of println" */
// OK (nested comment)
println_TEST_("Γειά σου, Κόσμε") // some comment // want "call of println"
// OK (nested comment in /**/)
println_TEST_("你好,世界") /* some comment // want "call of println" */
// OK (multiple expectations on same line)
println_TEST_()
println_TEST_() // want "call of println(...)" "call of println(...)"
// A Line that is not formatted correctly in the golden file.
}
// OK (facts and diagnostics on same line)
func println(...interface{}) { println_TEST_() } // want println:"found" "call of println(...)"
`,
"a/b_test.go": `package main
// Test file shouldn't mess with things (issue #40574)
`,
}
dir, cleanup, err := analysistest.WriteFiles(filemap)
if err != nil {
t.Fatal(err)
}
defer cleanup()
var got []string
t2 := errorfunc(func(s string) { got = append(got, s) }) // a fake *testing.T
analysistest.RunWithSuggestedFixes(t2, dir, findcall.Analyzer, "a")
want := []string{
`a/b.go:5: in 'want' comment: unexpected ":"`,
`a/b.go:6: in 'want' comment: got String after foo, want ':'`,
`a/b.go:7: in 'want' comment: got EOF, want regular expression`,
`a/b.go:8: in 'want' comment: invalid char escape`,
"a/b.go:11:9: diagnostic \"call of println(...)\" does not match pattern `wrong expectation text`",
`a/b.go:14:9: unexpected diagnostic: call of println(...)`,
"a/b.go:11: no diagnostic was reported matching `wrong expectation text`",
"a/b.go:17: no diagnostic was reported matching `unsatisfied expectation`",
// duplicate copies of each message from the test package (see issue #40574)
`a/b.go:5: in 'want' comment: unexpected ":"`,
`a/b.go:6: in 'want' comment: got String after foo, want ':'`,
`a/b.go:7: in 'want' comment: got EOF, want regular expression`,
`a/b.go:8: in 'want' comment: invalid char escape`,
"a/b.go:11:9: diagnostic \"call of println(...)\" does not match pattern `wrong expectation text`",
`a/b.go:14:9: unexpected diagnostic: call of println(...)`,
"a/b.go:11: no diagnostic was reported matching `wrong expectation text`",
"a/b.go:17: no diagnostic was reported matching `unsatisfied expectation`",
}
if !reflect.DeepEqual(got, want) {
t.Errorf("got:\n%s\nwant:\n%s",
strings.Join(got, "\n"),
strings.Join(want, "\n"))
}
}
// TestNoEnd tests that a missing SuggestedFix.End position is
// correctly interpreted as if equal to SuggestedFix.Pos (see issue #64199).
func TestNoEnd(t *testing.T) {
noend := &analysis.Analyzer{
Name: "noend",
Doc: "inserts /*hello*/ before first decl",
Run: func(pass *analysis.Pass) (any, error) {
decl := pass.Files[0].Decls[0]
pass.Report(analysis.Diagnostic{
Pos: decl.Pos(),
End: token.NoPos,
Message: "say hello",
SuggestedFixes: []analysis.SuggestedFix{{
Message: "say hello",
TextEdits: []analysis.TextEdit{
{
Pos: decl.Pos(),
End: token.NoPos,
NewText: []byte("/*hello*/"),
},
},
}},
})
return nil, nil
},
}
filemap := map[string]string{
"a/a.go": `package a
func F() {} // want "say hello"`,
"a/a.go.golden": `package a
/*hello*/
func F() {} // want "say hello"`,
}
dir, cleanup, err := analysistest.WriteFiles(filemap)
if err != nil {
t.Fatal(err)
}
defer cleanup()
analysistest.RunWithSuggestedFixes(t, dir, noend, "a")
}
func TestModule(t *testing.T) {
const content = `
Test that analysis.pass.Module is populated.
-- go.mod --
module golang.org/fake/mod
go 1.21
require golang.org/xyz/fake v0.12.34
-- mod.go --
// We expect a module.Path and a module.GoVersion, but an empty module.Version.
package mod // want "golang.org/fake/mod,,1.21"
import "golang.org/xyz/fake/ver"
var _ ver.T
-- vendor/modules.txt --
# golang.org/xyz/fake v0.12.34
## explicit; go 1.18
golang.org/xyz/fake/ver
-- vendor/golang.org/xyz/fake/ver/ver.go --
// This package is vendored so that we can populate a non-empty
// Pass.Module.Version is in a test.
package ver //want "golang.org/xyz/fake,v0.12.34,1.18"
type T string
`
fs, err := txtar.FS(txtar.Parse([]byte(content)))
if err != nil {
t.Fatal(err)
}
dir := testfiles.CopyToTmp(t, fs)
filever := &analysis.Analyzer{
Name: "mod",
Doc: "reports module information",
Run: func(pass *analysis.Pass) (any, error) {
msg := "no module info"
if m := pass.Module; m != nil {
msg = fmt.Sprintf("%s,%s,%s", m.Path, m.Version, m.GoVersion)
}
for _, file := range pass.Files {
pass.Reportf(file.Package, "%s", msg)
}
return nil, nil
},
}
analysistest.Run(t, dir, filever, "golang.org/fake/mod", "golang.org/xyz/fake/ver")
}
type errorfunc func(string)
func (f errorfunc) Errorf(format string, args ...any) {
f(fmt.Sprintf(format, args...))
}
================================================
FILE: go/analysis/checker/checker.go
================================================
// Copyright 2024 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package checker provides an analysis driver based on the
// [golang.org/x/tools/go/packages] representation of a set of
// packages and all their dependencies, as produced by
// [packages.Load].
//
// It is the core of multichecker (the multi-analyzer driver),
// singlechecker (the single-analyzer driver often used to provide a
// convenient command alongside each analyzer), and analysistest, the
// test driver.
//
// By contrast, the 'go vet' command is based on unitchecker, an
// analysis driver that uses separate analysis--analogous to separate
// compilation--with file-based intermediate results. Like separate
// compilation, it is more scalable, especially for incremental
// analysis of large code bases. Commands based on multichecker and
// singlechecker are capable of detecting when they are being invoked
// by "go vet -vettool=exe" and instead dispatching to unitchecker.
//
// Programs built using this package will, in general, not be usable
// in that way. This package is intended only for use in applications
// that invoke the analysis driver as a subroutine, and need to insert
// additional steps before or after the analysis.
//
// See the Example of how to build a complete analysis driver program.
package checker
import (
"bytes"
"encoding/gob"
"fmt"
"go/types"
"io"
"iter"
"log"
"os"
"reflect"
"sort"
"strings"
"sync"
"time"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/internal"
"golang.org/x/tools/go/packages"
"golang.org/x/tools/internal/analysis/driverutil"
)
// Options specifies options that control the analysis driver.
type Options struct {
// These options correspond to existing flags exposed by multichecker:
Sequential bool // disable parallelism
SanityCheck bool // check fact encoding is ok and deterministic
FactLog io.Writer // if non-nil, log each exported fact to it
// TODO(adonovan): expose ReadFile so that an Overlay specified
// in the [packages.Config] can be communicated via
// Pass.ReadFile to each Analyzer.
readFile driverutil.ReadFileFunc
}
// Graph holds the results of a round of analysis, including the graph
// of requested actions (analyzers applied to packages) plus any
// dependent actions that it was necessary to compute.
type Graph struct {
// Roots contains the roots of the action graph.
// Each node (a, p) in the action graph represents the
// application of one analyzer a to one package p.
// (A node thus corresponds to one analysis.Pass instance.)
// Roots holds one action per element of the product
// of the analyzers × packages arguments to Analyze,
// in unspecified order.
//
// Each element of Action.Deps represents an edge in the
// action graph: a dependency from one action to another.
// An edge of the form (a, p) -> (a, p2) indicates that the
// analysis of package p requires information ("facts") from
// the same analyzer applied to one of p's dependencies, p2.
// An edge of the form (a, p) -> (a2, p) indicates that the
// analysis of package p requires information ("results")
// from a different analyzer a2 applied to the same package.
// These two kind of edges are called "vertical" and "horizontal",
// respectively.
Roots []*Action
}
// All returns an iterator over the action graph in depth-first postorder.
//
// Example:
//
// for act := range graph.All() {
// ...
// }
func (g *Graph) All() iter.Seq[*Action] {
return func(yield func(*Action) bool) {
forEach(g.Roots, func(act *Action) error {
if !yield(act) {
return io.EOF // any error will do
}
return nil
}) // ignore error
}
}
// An Action represents one unit of analysis work by the driver: the
// application of one analysis to one package. It provides the inputs
// to and records the outputs of a single analysis.Pass.
//
// Actions form a DAG, both within a package (as different analyzers
// are applied, either in sequence or parallel), and across packages
// (as dependencies are analyzed).
type Action struct {
Analyzer *analysis.Analyzer
Package *packages.Package
IsRoot bool // whether this is a root node of the graph
Deps []*Action
Result any // computed result of Analyzer.run, if any (and if IsRoot)
Err error // error result of Analyzer.run
Diagnostics []analysis.Diagnostic
Duration time.Duration // execution time of this step
opts *Options
once sync.Once
pass *analysis.Pass
objectFacts map[objectFactKey]analysis.Fact
packageFacts map[packageFactKey]analysis.Fact
}
func (act *Action) String() string {
return fmt.Sprintf("%s@%s", act.Analyzer, act.Package)
}
// Analyze runs the specified analyzers on the initial packages.
//
// The initial packages and all dependencies must have been loaded
// using the [packages.LoadAllSyntax] flag, Analyze may need to run
// some analyzer (those that consume and produce facts) on
// dependencies too.
//
// On success, it returns a Graph of actions whose Roots hold one
// item per (a, p) in the cross-product of analyzers and pkgs.
//
// If opts is nil, it is equivalent to new(Options).
func Analyze(analyzers []*analysis.Analyzer, pkgs []*packages.Package, opts *Options) (*Graph, error) {
if opts == nil {
opts = new(Options)
}
if err := analysis.Validate(analyzers); err != nil {
return nil, err
}
// Construct the action graph.
//
// Each graph node (action) is one unit of analysis.
// Edges express package-to-package (vertical) dependencies,
// and analysis-to-analysis (horizontal) dependencies.
type key struct {
a *analysis.Analyzer
pkg *packages.Package
}
actions := make(map[key]*Action)
var mkAction func(a *analysis.Analyzer, pkg *packages.Package) *Action
mkAction = func(a *analysis.Analyzer, pkg *packages.Package) *Action {
k := key{a, pkg}
act, ok := actions[k]
if !ok {
act = &Action{Analyzer: a, Package: pkg, opts: opts}
// Add a dependency on each required analyzers.
for _, req := range a.Requires {
act.Deps = append(act.Deps, mkAction(req, pkg))
}
// An analysis that consumes/produces facts
// must run on the package's dependencies too.
if len(a.FactTypes) > 0 {
paths := make([]string, 0, len(pkg.Imports))
for path := range pkg.Imports {
paths = append(paths, path)
}
sort.Strings(paths) // for determinism
for _, path := range paths {
dep := mkAction(a, pkg.Imports[path])
act.Deps = append(act.Deps, dep)
}
}
actions[k] = act
}
return act
}
// Build nodes for initial packages.
var roots []*Action
for _, a := range analyzers {
for _, pkg := range pkgs {
root := mkAction(a, pkg)
root.IsRoot = true
roots = append(roots, root)
}
}
// Execute the graph in parallel.
execAll(roots)
// Ensure that only root Results are visible to caller.
// (The others are considered temporary intermediaries.)
// TODO(adonovan): opt: clear them earlier, so we can
// release large data structures like SSA sooner.
for _, act := range actions {
if !act.IsRoot {
act.Result = nil
}
}
return &Graph{Roots: roots}, nil
}
func init() {
// Allow analysistest to access Action.pass,
// for the legacy analysistest.Result data type,
// and for internal/checker.ApplyFixes to access pass.ReadFile.
internal.ActionPass = func(x any) *analysis.Pass { return x.(*Action).pass }
}
type objectFactKey struct {
obj types.Object
typ reflect.Type
}
type packageFactKey struct {
pkg *types.Package
typ reflect.Type
}
func execAll(actions []*Action) {
var wg sync.WaitGroup
for _, act := range actions {
wg.Add(1)
work := func(act *Action) {
act.exec()
wg.Done()
}
if act.opts.Sequential {
work(act)
} else {
go work(act)
}
}
wg.Wait()
}
func (act *Action) exec() { act.once.Do(act.execOnce) }
func (act *Action) execOnce() {
// Analyze dependencies.
execAll(act.Deps)
// Record time spent in this node but not its dependencies.
// In parallel mode, due to GC/scheduler contention, the
// time is 5x higher than in sequential mode, even with a
// semaphore limiting the number of threads here.
// So use -debug=tp.
t0 := time.Now()
defer func() { act.Duration = time.Since(t0) }()
// Report an error if any dependency failed.
var failed []string
for _, dep := range act.Deps {
if dep.Err != nil {
failed = append(failed, dep.String())
}
}
if failed != nil {
sort.Strings(failed)
act.Err = fmt.Errorf("failed prerequisites: %s", strings.Join(failed, ", "))
return
}
// Plumb the output values of the dependencies
// into the inputs of this action. Also facts.
inputs := make(map[*analysis.Analyzer]any)
act.objectFacts = make(map[objectFactKey]analysis.Fact)
act.packageFacts = make(map[packageFactKey]analysis.Fact)
for _, dep := range act.Deps {
if dep.Package == act.Package {
// Same package, different analysis (horizontal edge):
// in-memory outputs of prerequisite analyzers
// become inputs to this analysis pass.
inputs[dep.Analyzer] = dep.Result
} else if dep.Analyzer == act.Analyzer { // (always true)
// Same analysis, different package (vertical edge):
// serialized facts produced by prerequisite analysis
// become available to this analysis pass.
inheritFacts(act, dep)
}
}
// Quick (nonexhaustive) check that the correct go/packages mode bits were used.
// (If there were errors, all bets are off.)
if pkg := act.Package; pkg.Errors == nil {
if pkg.Name == "" || pkg.PkgPath == "" || pkg.Types == nil || pkg.Fset == nil || pkg.TypesSizes == nil {
panic("packages must be loaded with packages.LoadSyntax mode")
}
}
module := &analysis.Module{} // possibly empty (non nil) in go/analysis drivers.
if mod := act.Package.Module; mod != nil {
module = analysisModuleFromPackagesModule(mod)
}
// Run the analysis.
pass := &analysis.Pass{
Analyzer: act.Analyzer,
Fset: act.Package.Fset,
Files: act.Package.Syntax,
OtherFiles: act.Package.OtherFiles,
IgnoredFiles: act.Package.IgnoredFiles,
Pkg: act.Package.Types,
TypesInfo: act.Package.TypesInfo,
TypesSizes: act.Package.TypesSizes,
TypeErrors: act.Package.TypeErrors,
Module: module,
ResultOf: inputs,
Report: func(d analysis.Diagnostic) {
// Assert that SuggestedFixes are well formed.
if err := driverutil.ValidateFixes(act.Package.Fset, act.Analyzer, d.SuggestedFixes); err != nil {
panic(err)
}
act.Diagnostics = append(act.Diagnostics, d)
},
ImportObjectFact: act.ObjectFact,
ExportObjectFact: act.exportObjectFact,
ImportPackageFact: act.PackageFact,
ExportPackageFact: act.exportPackageFact,
AllObjectFacts: act.AllObjectFacts,
AllPackageFacts: act.AllPackageFacts,
}
readFile := os.ReadFile
if act.opts.readFile != nil {
readFile = act.opts.readFile
}
pass.ReadFile = driverutil.CheckedReadFile(pass, readFile)
act.pass = pass
act.Result, act.Err = func() (any, error) {
if act.Package.IllTyped && !pass.Analyzer.RunDespiteErrors {
return nil, fmt.Errorf("analysis skipped due to errors in package")
}
result, err := pass.Analyzer.Run(pass)
if err != nil {
return nil, err
}
// correct result type?
if got, want := reflect.TypeOf(result), pass.Analyzer.ResultType; got != want {
return nil, fmt.Errorf(
"internal error: on package %s, analyzer %s returned a result of type %v, but declared ResultType %v",
pass.Pkg.Path(), pass.Analyzer, got, want)
}
// resolve diagnostic URLs
for i := range act.Diagnostics {
url, err := driverutil.ResolveURL(act.Analyzer, act.Diagnostics[i])
if err != nil {
return nil, err
}
act.Diagnostics[i].URL = url
}
return result, nil
}()
// Help detect (disallowed) calls after Run.
pass.ExportObjectFact = nil
pass.ExportPackageFact = nil
}
// inheritFacts populates act.facts with
// those it obtains from its dependency, dep.
func inheritFacts(act, dep *Action) {
for key, fact := range dep.objectFacts {
// Filter out facts related to objects
// that are irrelevant downstream
// (equivalently: not in the compiler export data).
if !exportedFrom(key.obj, dep.Package.Types) {
if false {
log.Printf("%v: discarding %T fact from %s for %s: %s", act, fact, dep, key.obj, fact)
}
continue
}
// Optionally serialize/deserialize fact
// to verify that it works across address spaces.
if act.opts.SanityCheck {
encodedFact, err := codeFact(fact)
if err != nil {
log.Panicf("internal error: encoding of %T fact failed in %v", fact, act)
}
fact = encodedFact
}
if false {
log.Printf("%v: inherited %T fact for %s: %s", act, fact, key.obj, fact)
}
act.objectFacts[key] = fact
}
for key, fact := range dep.packageFacts {
// TODO: filter out facts that belong to
// packages not mentioned in the export data
// to prevent side channels.
//
// The Pass.All{Object,Package}Facts accessors expose too much:
// all facts, of all types, for all dependencies in the action
// graph. Not only does the representation grow quadratically,
// but it violates the separate compilation paradigm, allowing
// analysis implementations to communicate with indirect
// dependencies that are not mentioned in the export data.
//
// It's not clear how to fix this short of a rather expensive
// filtering step after each action that enumerates all the
// objects that would appear in export data, and deletes
// facts associated with objects not in this set.
// Optionally serialize/deserialize fact
// to verify that it works across address spaces
// and is deterministic.
if act.opts.SanityCheck {
encodedFact, err := codeFact(fact)
if err != nil {
log.Panicf("internal error: encoding of %T fact failed in %v", fact, act)
}
fact = encodedFact
}
if false {
log.Printf("%v: inherited %T fact for %s: %s", act, fact, key.pkg.Path(), fact)
}
act.packageFacts[key] = fact
}
}
// codeFact encodes then decodes a fact,
// just to exercise that logic.
func codeFact(fact analysis.Fact) (analysis.Fact, error) {
// We encode facts one at a time.
// A real modular driver would emit all facts
// into one encoder to improve gob efficiency.
var buf bytes.Buffer
if err := gob.NewEncoder(&buf).Encode(fact); err != nil {
return nil, err
}
// Encode it twice and assert that we get the same bits.
// This helps detect nondeterministic Gob encoding (e.g. of maps).
var buf2 bytes.Buffer
if err := gob.NewEncoder(&buf2).Encode(fact); err != nil {
return nil, err
}
if !bytes.Equal(buf.Bytes(), buf2.Bytes()) {
return nil, fmt.Errorf("encoding of %T fact is nondeterministic", fact)
}
new := reflect.New(reflect.TypeOf(fact).Elem()).Interface().(analysis.Fact)
if err := gob.NewDecoder(&buf).Decode(new); err != nil {
return nil, err
}
return new, nil
}
// exportedFrom reports whether obj may be visible to a package that imports pkg.
// This includes not just the exported members of pkg, but also unexported
// constants, types, fields, and methods, perhaps belonging to other packages,
// that find there way into the API.
// This is an overapproximation of the more accurate approach used by
// gc export data, which walks the type graph, but it's much simpler.
//
// TODO(adonovan): do more accurate filtering by walking the type graph.
func exportedFrom(obj types.Object, pkg *types.Package) bool {
switch obj := obj.(type) {
case *types.Func:
return obj.Exported() && obj.Pkg() == pkg ||
obj.Signature().Recv() != nil
case *types.Var:
if obj.IsField() {
return true
}
// we can't filter more aggressively than this because we need
// to consider function parameters exported, but have no way
// of telling apart function parameters from local variables.
return obj.Pkg() == pkg
case *types.TypeName, *types.Const:
return true
}
return false // Nil, Builtin, Label, or PkgName
}
// ObjectFact retrieves a fact associated with obj,
// and returns true if one was found.
// Given a value ptr of type *T, where *T satisfies Fact,
// ObjectFact copies the value to *ptr.
//
// See documentation at ImportObjectFact field of [analysis.Pass].
func (act *Action) ObjectFact(obj types.Object, ptr analysis.Fact) bool {
if obj == nil {
panic("nil object")
}
key := objectFactKey{obj, factType(ptr)}
if v, ok := act.objectFacts[key]; ok {
reflect.ValueOf(ptr).Elem().Set(reflect.ValueOf(v).Elem())
return true
}
return false
}
// exportObjectFact implements Pass.ExportObjectFact.
func (act *Action) exportObjectFact(obj types.Object, fact analysis.Fact) {
if act.pass.ExportObjectFact == nil {
log.Panicf("%s: Pass.ExportObjectFact(%s, %T) called after Run", act, obj, fact)
}
if obj.Pkg() != act.Package.Types {
log.Panicf("internal error: in analysis %s of package %s: Fact.Set(%s, %T): can't set facts on objects belonging another package",
act.Analyzer, act.Package, obj, fact)
}
key := objectFactKey{obj, factType(fact)}
act.objectFacts[key] = fact // clobber any existing entry
if log := act.opts.FactLog; log != nil {
objstr := types.ObjectString(obj, (*types.Package).Name)
fmt.Fprintf(log, "%s: object %s has fact %s\n",
act.Package.Fset.Position(obj.Pos()), objstr, fact)
}
}
// AllObjectFacts returns a new slice containing all object facts of
// the analysis's FactTypes in unspecified order.
//
// See documentation at AllObjectFacts field of [analysis.Pass].
func (act *Action) AllObjectFacts() []analysis.ObjectFact {
facts := make([]analysis.ObjectFact, 0, len(act.objectFacts))
for k, fact := range act.objectFacts {
facts = append(facts, analysis.ObjectFact{Object: k.obj, Fact: fact})
}
return facts
}
// PackageFact retrieves a fact associated with package pkg,
// which must be this package or one of its dependencies.
//
// See documentation at ImportObjectFact field of [analysis.Pass].
func (act *Action) PackageFact(pkg *types.Package, ptr analysis.Fact) bool {
if pkg == nil {
panic("nil package")
}
key := packageFactKey{pkg, factType(ptr)}
if v, ok := act.packageFacts[key]; ok {
reflect.ValueOf(ptr).Elem().Set(reflect.ValueOf(v).Elem())
return true
}
return false
}
// exportPackageFact implements Pass.ExportPackageFact.
func (act *Action) exportPackageFact(fact analysis.Fact) {
if act.pass.ExportPackageFact == nil {
log.Panicf("%s: Pass.ExportPackageFact(%T) called after Run", act, fact)
}
key := packageFactKey{act.pass.Pkg, factType(fact)}
act.packageFacts[key] = fact // clobber any existing entry
if log := act.opts.FactLog; log != nil {
fmt.Fprintf(log, "%s: package %s has fact %s\n",
act.Package.Fset.Position(act.pass.Files[0].Pos()), act.pass.Pkg.Path(), fact)
}
}
func factType(fact analysis.Fact) reflect.Type {
t := reflect.TypeOf(fact)
if t.Kind() != reflect.Pointer {
log.Fatalf("invalid Fact type: got %T, want pointer", fact)
}
return t
}
// AllPackageFacts returns a new slice containing all package
// facts of the analysis's FactTypes in unspecified order.
//
// See documentation at AllPackageFacts field of [analysis.Pass].
func (act *Action) AllPackageFacts() []analysis.PackageFact {
facts := make([]analysis.PackageFact, 0, len(act.packageFacts))
for k, fact := range act.packageFacts {
facts = append(facts, analysis.PackageFact{Package: k.pkg, Fact: fact})
}
return facts
}
// forEach is a utility function for traversing the action graph. It
// applies function f to each action in the graph reachable from
// roots, in depth-first postorder. If any call to f returns an error,
// the traversal is aborted and ForEach returns the error.
func forEach(roots []*Action, f func(*Action) error) error {
seen := make(map[*Action]bool)
var visitAll func(actions []*Action) error
visitAll = func(actions []*Action) error {
for _, act := range actions {
if !seen[act] {
seen[act] = true
if err := visitAll(act.Deps); err != nil {
return err
}
if err := f(act); err != nil {
return err
}
}
}
return nil
}
return visitAll(roots)
}
func analysisModuleFromPackagesModule(mod *packages.Module) *analysis.Module {
if mod == nil {
return nil
}
var modErr *analysis.ModuleError
if mod.Error != nil {
modErr = &analysis.ModuleError{
Err: mod.Error.Err,
}
}
return &analysis.Module{
Path: mod.Path,
Version: mod.Version,
Replace: analysisModuleFromPackagesModule(mod.Replace),
Time: mod.Time,
Main: mod.Main,
Indirect: mod.Indirect,
Dir: mod.Dir,
GoMod: mod.GoMod,
GoVersion: mod.GoVersion,
Error: modErr,
}
}
================================================
FILE: go/analysis/checker/checker_test.go
================================================
// Copyright 2026 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package checker_test
import (
"os"
"path/filepath"
"testing"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/checker"
"golang.org/x/tools/go/packages"
"golang.org/x/tools/internal/testenv"
"golang.org/x/tools/txtar"
)
// TestPassModule checks that an analyzer observes the correct Pass.Module
// fields (GoMod, Dir, Path, Main, GoVersion) when run on a module.
func TestPassModule(t *testing.T) {
testenv.NeedsGoPackages(t)
const src = `
-- go.mod --
module example.com/hello
go 1.13
-- hello.go --
package hello
`
dir, err := os.MkdirTemp("", "")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(dir)
fs, err := txtar.FS(txtar.Parse([]byte(src)))
if err != nil {
t.Fatal(err)
}
if err := os.CopyFS(dir, fs); err != nil {
t.Fatal(err)
}
// Capture the Module seen by the analyzer.
var got *analysis.Module
testAnalyzer := &analysis.Analyzer{
Name: "testmodule",
Doc: "Captures Pass.Module for testing.",
Run: func(pass *analysis.Pass) (any, error) {
got = pass.Module
return nil, nil
},
}
cfg := &packages.Config{
Mode: packages.LoadAllSyntax | packages.NeedModule,
Dir: dir,
}
pkgs, err := packages.Load(cfg, "example.com/hello")
if err != nil {
t.Fatal(err)
}
if _, err := checker.Analyze([]*analysis.Analyzer{testAnalyzer}, pkgs, nil); err != nil {
t.Fatal(err)
}
if got == nil {
t.Fatal("Pass.Module is nil")
}
if got.Path != "example.com/hello" {
t.Errorf("Pass.Module.Path = %q, want %q", got.Path, "example.com/hello")
}
if !got.Main {
t.Errorf("Pass.Module.Main = false, want true")
}
wantGoMod := filepath.Join(dir, "go.mod")
if got.GoMod != wantGoMod {
t.Errorf("Pass.Module.GoMod = %q, want %q", got.GoMod, wantGoMod)
}
if got.Dir != dir {
t.Errorf("Pass.Module.Dir = %q, want %q", got.Dir, dir)
}
if got.GoVersion != "1.13" {
t.Errorf("Pass.Module.GoVersion = %q, want %q", got.GoVersion, "1.13")
}
}
================================================
FILE: go/analysis/checker/example_test.go
================================================
// Copyright 2024 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build !wasm
// The example command demonstrates a simple go/packages-based
// analysis driver program.
package checker_test
import (
"fmt"
"log"
"maps"
"os"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/checker"
"golang.org/x/tools/go/packages"
"golang.org/x/tools/txtar"
)
const testdata = `
-- go.mod --
module example.com
go 1.21
-- a/a.go --
package a
import _ "example.com/b"
import _ "example.com/c"
func A1()
func A2()
func A3()
-- b/b.go --
package b
func B1()
func B2()
-- c/c.go --
package c
import _ "example.com/d"
func C1()
-- d/d.go --
package d
func D1()
func D2()
func D3()
func D4()
`
func Example() {
// Extract a tree of Go source files.
// (Avoid the standard library as it is always evolving.)
dir, err := os.MkdirTemp("", "")
if err != nil {
log.Fatal(err)
}
fs, err := txtar.FS(txtar.Parse([]byte(testdata)))
if err != nil {
log.Fatal(err)
}
if err := os.CopyFS(dir, fs); err != nil {
log.Fatal(err)
}
// Load packages: example.com/a + dependencies
//
cfg := &packages.Config{Mode: packages.LoadAllSyntax, Dir: dir}
initial, err := packages.Load(cfg, "example.com/a")
if err != nil {
log.Fatal(err) // failure to enumerate packages
}
// There may be parse or type errors among the
// initial packages or their dependencies,
// but the analysis driver can handle faulty inputs,
// as can some analyzers.
packages.PrintErrors(initial)
if len(initial) == 0 {
log.Fatalf("no initial packages")
}
// Run analyzers (just one) on example.com packages.
analyzers := []*analysis.Analyzer{pkgdecls}
graph, err := checker.Analyze(analyzers, initial, nil)
if err != nil {
log.Fatal(err)
}
// Inspect the result of each analysis action,
// including those for all dependencies.
//
// A realistic client would use Result, Err, Diagnostics,
// but for test stability, we just print the action string
// ("analyzer@package").
for act := range graph.All() {
fmt.Println("printing", act)
}
// Print the package fact for the sole initial package.
root := graph.Roots[0]
fact := new(pkgdeclsFact)
if root.PackageFact(root.Package.Types, fact) {
for k, v := range fact.numdecls {
fmt.Printf("%s:\t%d decls\n", k, v)
}
}
// Unordered Output:
// printing pkgdecls@example.com/a
// printing pkgdecls@example.com/b
// printing pkgdecls@example.com/c
// printing pkgdecls@example.com/d
// example.com/a: 3 decls
// example.com/b: 2 decls
// example.com/c: 1 decls
// example.com/d: 4 decls
}
// pkgdecls is a trivial example analyzer that uses package facts to
// compute information from the entire dependency graph.
var pkgdecls = &analysis.Analyzer{
Name: "pkgdecls",
Doc: "Computes a package fact mapping each package to its number of declarations.",
Run: run,
FactTypes: []analysis.Fact{(*pkgdeclsFact)(nil)},
}
type pkgdeclsFact struct{ numdecls map[string]int }
func (*pkgdeclsFact) AFact() {}
func run(pass *analysis.Pass) (any, error) {
numdecls := map[string]int{
pass.Pkg.Path(): pass.Pkg.Scope().Len(),
}
// Compute the union across all dependencies.
for _, imp := range pass.Pkg.Imports() {
if depFact := new(pkgdeclsFact); pass.ImportPackageFact(imp, depFact) {
maps.Copy(numdecls, depFact.numdecls)
}
}
pass.ExportPackageFact(&pkgdeclsFact{numdecls})
return nil, nil
}
================================================
FILE: go/analysis/checker/print.go
================================================
// Copyright 2024 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package checker
// This file defines helpers for printing analysis results.
// They should all be pure functions.
import (
"bytes"
"fmt"
"go/token"
"io"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/internal/analysis/driverutil"
)
// PrintText emits diagnostics as plain text to w.
//
// If contextLines is nonnegative, it also prints the
// offending line, plus that many lines of context
// before and after the line.
func (g *Graph) PrintText(w io.Writer, contextLines int) error {
return writeTextDiagnostics(w, g.Roots, contextLines)
}
func writeTextDiagnostics(w io.Writer, roots []*Action, contextLines int) error {
// De-duplicate diagnostics by position (not token.Pos) to
// avoid double-reporting in source files that belong to
// multiple packages, such as foo and foo.test.
// (We cannot assume that such repeated files were parsed
// only once and use syntax nodes as the key.)
type key struct {
pos token.Position
end token.Position
*analysis.Analyzer
message string
}
seen := make(map[key]bool)
// TODO(adonovan): opt: plumb errors back from PrintPlain and avoid buffer.
buf := new(bytes.Buffer)
forEach(roots, func(act *Action) error {
if act.Err != nil {
fmt.Fprintf(w, "%s: %v\n", act.Analyzer.Name, act.Err)
} else if act.IsRoot {
for _, diag := range act.Diagnostics {
// We don't display Analyzer.Name/diag.Category
// as most users don't care.
posn := act.Package.Fset.Position(diag.Pos)
end := act.Package.Fset.Position(diag.End)
k := key{posn, end, act.Analyzer, diag.Message}
if seen[k] {
continue // duplicate
}
seen[k] = true
driverutil.PrintPlain(buf, act.Package.Fset, contextLines, diag)
}
}
return nil
})
_, err := w.Write(buf.Bytes())
return err
}
// PrintJSON emits diagnostics in JSON form to w.
// Diagnostics are shown only for the root nodes,
// but errors (if any) are shown for all dependencies.
func (g *Graph) PrintJSON(w io.Writer) error {
return writeJSONDiagnostics(w, g.Roots)
}
func writeJSONDiagnostics(w io.Writer, roots []*Action) error {
tree := make(driverutil.JSONTree)
forEach(roots, func(act *Action) error {
var diags []analysis.Diagnostic
if act.IsRoot {
diags = act.Diagnostics
}
tree.Add(act.Package.Fset, act.Package.ID, act.Analyzer.Name, diags, act.Err)
return nil
})
return tree.Print(w)
}
================================================
FILE: go/analysis/diagnostic.go
================================================
// Copyright 2019 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package analysis
import "go/token"
// A Diagnostic is a message associated with a source location or range.
//
// An Analyzer may return a variety of diagnostics; the optional Category,
// which should be a constant, may be used to classify them.
// It is primarily intended to make it easy to look up documentation.
//
// All Pos values are interpreted relative to Pass.Fset. If End is
// provided, the diagnostic is specified to apply to the range between
// Pos and End.
type Diagnostic struct {
Pos token.Pos
End token.Pos // optional
Category string // optional
Message string
// URL is the optional location of a web page that provides
// additional documentation for this diagnostic.
//
// If URL is empty but a Category is specified, then the
// Analysis driver should treat the URL as "#"+Category.
//
// The URL may be relative. If so, the base URL is that of the
// Analyzer that produced the diagnostic;
// see https://pkg.go.dev/net/url#URL.ResolveReference.
URL string
// SuggestedFixes is an optional list of fixes to address the
// problem described by the diagnostic. Each one represents an
// alternative strategy, and should have a distinct and
// descriptive message; at most one may be applied.
//
// Fixes for different diagnostics should be treated as
// independent changes to the same baseline file state,
// analogous to a set of git commits all with the same parent.
// Combining fixes requires resolving any conflicts that
// arise, analogous to a git merge.
// Any conflicts that remain may be dealt with, depending on
// the tool, by discarding fixes, consulting the user, or
// aborting the operation.
SuggestedFixes []SuggestedFix
// Related contains optional secondary positions and messages
// related to the primary diagnostic.
Related []RelatedInformation
}
// RelatedInformation contains information related to a diagnostic.
// For example, a diagnostic that flags duplicated declarations of a
// variable may include one RelatedInformation per existing
// declaration.
type RelatedInformation struct {
Pos token.Pos
End token.Pos // optional
Message string
}
// A SuggestedFix is a code change associated with a Diagnostic that a
// user can choose to apply to their code. Usually the SuggestedFix is
// meant to fix the issue flagged by the diagnostic.
//
// The TextEdits must not overlap, nor contain edits for other
// packages. Edits need not be totally ordered, but the order
// determines how insertions at the same point will be applied.
type SuggestedFix struct {
// A verb phrase describing the fix, to be shown to
// a user trying to decide whether to accept it.
//
// Example: "Remove the surplus argument"
Message string
TextEdits []TextEdit
}
// A TextEdit represents the replacement of the code between Pos and End with the new text.
// Each TextEdit should apply to a single file. End should not be earlier in the file than Pos.
type TextEdit struct {
// For a pure insertion, End can either be set to Pos or token.NoPos.
Pos token.Pos
End token.Pos
NewText []byte
}
================================================
FILE: go/analysis/doc/suggested_fixes.md
================================================
# Suggested Fixes in the Analysis Framework
## The Purpose of Suggested Fixes
The analysis framework is planned to add a facility to output
suggested fixes. Suggested fixes in the analysis framework
are meant to address two common use cases. The first is the
natural use case of allowing the user to quickly fix errors or issues
pointed out by analyzers through their editor or analysis tool.
An editor, when showing a diagnostic for an issue, can propose
code to fix that issue. Users can accept the proposal and have
the editor apply the fix for them. The second case is to allow
for defining refactorings. An analyzer meant to perform a
refactoring can produce suggested fixes equivalent to the diff
of the refactoring. Then, an analysis driver meant to apply
refactorings can automatically apply all the diffs that
are produced by the analysis as suggested fixes.
## Proposed Suggested Fix API
Suggested fixes will be defined using the following structs:
```go
// A SuggestedFix is a code change associated with a Diagnostic that a user can choose
// to apply to their code. Usually the SuggestedFix is meant to fix the issue flagged
// by the diagnostic.
type SuggestedFix struct {
// A description for this suggested fix to be shown to a user deciding
// whether to accept it.
Message string
TextEdits []TextEdit
}
// A TextEdit represents the replacement of the code between Pos and End with the new text.
type TextEdit struct {
// For a pure insertion, End can either be set to Pos or token.NoPos.
Pos token.Pos
End token.Pos
NewText []byte
}
```
A suggested fix needs a message field so it can specify what it will do.
Some analyses may not have clear cut fixes, and a suggested fix may need
to provide additional information to help users specify whether they
should be added.
Suggested fixes are allowed to make multiple
edits in a file, because some logical changes may affect otherwise
unrelated parts of the AST.
A TextEdit specifies a Pos and End: these will usually be the Pos
and End of an AST node that will be replaced.
Finally, the replacements themselves are represented as []bytes.
Suggested fixes themselves will be added as a field in the
Diagnostic struct:
```go
type Diagnostic struct {
...
SuggestedFixes []SuggestedFix // this is an optional field
}
```
### Requirements for SuggestedFixes
SuggestedFixes will be required to conform to several requirements:
* TextEdits for a SuggestedFix should not overlap.
* TextEdits for SuggestedFixes should not contain edits for other packages.
* Each TextEdit should apply to a single file.
These requirements guarantee that suggested fixes can be cleanly applied.
Because a driver may only analyze, or be able to modify, the current package,
we restrict edits to the current package. In general this restriction should
not be a big problem for users because other packages might not belong to the
same module and so will not be safe to modify in a single change.
On the other hand, analyzers will not be required to produce gofmt-compliant
code. Analysis drivers will be expected to apply gofmt to the results of
a SuggestedFix application.
## SuggestedFix integration points
### ```checker -fix```
Singlechecker and multichecker have the ```-fix``` flag, which will automatically
apply all fixes suggested by their analysis or analyses. This is intended to
be used primarily by refactoring tools, because in general, like diagnostics,
suggested fixes will need to be examined by a human who can decide whether
they are relevant.
### gopls
Suggested fixes have been integrated into ```gopls```, and editors can choose
to display the suggested fixes to the user as they type, so that they can be
accepted to fix diagnostics immediately.
### Code Review Tools (Future Work)
Suggested fixes can be integrated into programs that are integrated with
code review systems to suggest fixes that users can apply from their code review tools.
## Alternatives
### Performing transformations directly on the AST
Even though it may be more convenient
for authors of refactorings to perform transformations directly on
the AST, allowing mutations on the AST would mean that a copy of the AST
would need to be made every time a transformation was produced, to avoid
having transformations interfere with each other.
This is primarily an issue with the current design of the Go AST and
it's possible that a new future version of the AST might make this a more
viable option.
### Supplying AST nodes directly
Another possibility would be for SuggestedFixes to supply the replacement
ASTs directly. There is one primary limitation to this: that because
comments to ASTs specify their location using token.Pos values, it's very
difficult to place any comments in the right place.
In general, it's also more difficult to generate the AST structures for
some code than to generate the text for that code. So we prefer to allow
the flexibility to do the latter.
Because users can call ```format.Node``` to produce the text for any
AST node, users will always be able to produce a SuggestedFix from AST
nodes. In future, we may choose to add a convenience method that does this for users.
================================================
FILE: go/analysis/doc.go
================================================
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
/*
Package analysis defines the interface between a modular static
analysis and an analysis driver program.
# Background
A static analysis is a function that inspects a package of Go code and
reports a set of diagnostics (typically mistakes in the code), and
perhaps produces other results as well, such as suggested refactorings
or other facts. An analysis that reports mistakes is informally called a
"checker". For example, the printf checker reports mistakes in
fmt.Printf format strings.
A "modular" analysis is one that inspects one package at a time but can
save information from a lower-level package and use it when inspecting a
higher-level package, analogous to separate compilation in a toolchain.
The printf checker is modular: when it discovers that a function such as
log.Fatalf delegates to fmt.Printf, it records this fact, and checks
calls to that function too, including calls made from another package.
By implementing a common interface, checkers from a variety of sources
can be easily selected, incorporated, and reused in a wide range of
driver programs including command-line tools (such as vet), text editors and
IDEs, build and test systems (such as go build, Bazel, or Buck), test
frameworks, code review tools, code-base indexers (such as SourceGraph),
documentation viewers (such as godoc), batch pipelines for large code
bases, and so on.
# Analyzer
The primary type in the API is [Analyzer]. An Analyzer statically
describes an analysis function: its name, documentation, flags,
relationship to other analyzers, and of course, its logic.
To define an analysis, a user declares a (logically constant) variable
of type Analyzer. Here is a typical example from one of the analyzers in
the go/analysis/passes/ subdirectory:
package unusedresult
var Analyzer = &analysis.Analyzer{
Name: "unusedresult",
Doc: "check for unused results of calls to some functions",
Run: run,
...
}
func run(pass *analysis.Pass) (interface{}, error) {
...
}
An analysis driver is a program such as vet that runs a set of
analyses and prints the diagnostics that they report.
The driver program must import the list of Analyzers it needs.
Typically each Analyzer resides in a separate package.
To add a new Analyzer to an existing driver, add another item to the list:
import ( "unusedresult"; "nilness"; "printf" )
var analyses = []*analysis.Analyzer{
unusedresult.Analyzer,
nilness.Analyzer,
printf.Analyzer,
}
A driver may use the name, flags, and documentation to provide on-line
help that describes the analyses it performs.
The doc comment contains a brief one-line summary,
optionally followed by paragraphs of explanation.
The [Analyzer] type has more fields besides those shown above:
type Analyzer struct {
Name string
Doc string
Flags flag.FlagSet
Run func(*Pass) (interface{}, error)
RunDespiteErrors bool
ResultType reflect.Type
Requires []*Analyzer
FactTypes []Fact
}
The Flags field declares a set of named (global) flag variables that
control analysis behavior. Unlike vet, analysis flags are not declared
directly in the command line FlagSet; it is up to the driver to set the
flag variables. A driver for a single analysis, a, might expose its flag
f directly on the command line as -f, whereas a driver for multiple
analyses might prefix the flag name by the analysis name (-a.f) to avoid
ambiguity. An IDE might expose the flags through a graphical interface,
and a batch pipeline might configure them from a config file.
See the "findcall" analyzer for an example of flags in action.
The RunDespiteErrors flag indicates whether the analysis is equipped to
handle ill-typed code. If not, the driver will skip the analysis if
there were parse or type errors.
The optional ResultType field specifies the type of the result value
computed by this analysis and made available to other analyses.
The Requires field specifies a list of analyses upon which
this one depends and whose results it may access, and it constrains the
order in which a driver may run analyses.
The FactTypes field is discussed in the section on Modularity.
The analysis package provides a Validate function to perform basic
sanity checks on an Analyzer, such as that its Requires graph is
acyclic, its fact and result types are unique, and so on.
Finally, the Run field contains a function to be called by the driver to
execute the analysis on a single package. The driver passes it an
instance of the Pass type.
# Pass
A [Pass] describes a single unit of work: the application of a particular
Analyzer to a particular package of Go code.
The Pass provides information to the Analyzer's Run function about the
package being analyzed, and provides operations to the Run function for
reporting diagnostics and other information back to the driver.
type Pass struct {
Fset *token.FileSet
Files []*ast.File
OtherFiles []string
IgnoredFiles []string
Pkg *types.Package
TypesInfo *types.Info
ResultOf map[*Analyzer]interface{}
Report func(Diagnostic)
...
}
The Fset, Files, Pkg, and TypesInfo fields provide the syntax trees,
type information, and source positions for a single package of Go code.
The OtherFiles field provides the names of non-Go
files such as assembly that are part of this package.
Similarly, the IgnoredFiles field provides the names of Go and non-Go
source files that are not part of this package with the current build
configuration but may be part of other build configurations.
The contents of these files may be read using Pass.ReadFile;
see the "asmdecl" or "buildtags" analyzers for examples of loading
non-Go files and reporting diagnostics against them.
The ResultOf field provides the results computed by the analyzers
required by this one, as expressed in its Analyzer.Requires field. The
driver runs the required analyzers first and makes their results
available in this map. Each Analyzer must return a value of the type
described in its Analyzer.ResultType field.
For example, the "ctrlflow" analyzer returns a *ctrlflow.CFGs, which
provides a control-flow graph for each function in the package (see
golang.org/x/tools/go/cfg); the "inspect" analyzer returns a value that
enables other Analyzers to traverse the syntax trees of the package more
efficiently; and the "buildssa" analyzer constructs an SSA-form
intermediate representation.
Each of these Analyzers extends the capabilities of later Analyzers
without adding a dependency to the core API, so an analysis tool pays
only for the extensions it needs.
The Report function emits a diagnostic, a message associated with a
source position. For most analyses, diagnostics are their primary
result.
For convenience, Pass provides a helper method, Reportf, to report a new
diagnostic by formatting a string.
Diagnostic is defined as:
type Diagnostic struct {
Pos token.Pos
Category string // optional
Message string
}
The optional Category field is a short identifier that classifies the
kind of message when an analysis produces several kinds of diagnostic.
The [Diagnostic] struct does not have a field to indicate its severity
because opinions about the relative importance of Analyzers and their
diagnostics vary widely among users. The design of this framework does
not hold each Analyzer responsible for identifying the severity of its
diagnostics. Instead, we expect that drivers will allow the user to
customize the filtering and prioritization of diagnostics based on the
producing Analyzer and optional Category, according to the user's
preferences.
Most Analyzers inspect typed Go syntax trees, but a few, such as asmdecl
and buildtag, inspect the raw text of Go source files or even non-Go
files such as assembly. To report a diagnostic against a line of a
raw text file, use the following sequence:
content, err := pass.ReadFile(filename)
if err != nil { ... }
tf := fset.AddFile(filename, -1, len(content))
tf.SetLinesForContent(content)
...
pass.Reportf(tf.LineStart(line), "oops")
# Modular analysis with Facts
To improve efficiency and scalability, large programs are routinely
built using separate compilation: units of the program are compiled
separately, and recompiled only when one of their dependencies changes;
independent modules may be compiled in parallel. The same technique may
be applied to static analyses, for the same benefits. Such analyses are
described as "modular".
A compiler’s type checker is an example of a modular static analysis.
Many other checkers we would like to apply to Go programs can be
understood as alternative or non-standard type systems. For example,
vet's printf checker infers whether a function has the "printf wrapper"
type, and it applies stricter checks to calls of such functions. In
addition, it records which functions are printf wrappers for use by
later analysis passes to identify other printf wrappers by induction.
A result such as “f is a printf wrapper” that is not interesting by
itself but serves as a stepping stone to an interesting result (such as
a diagnostic) is called a [Fact].
The analysis API allows an analysis to define new types of facts, to
associate facts of these types with objects (named entities) declared
within the current package, or with the package as a whole, and to query
for an existing fact of a given type associated with an object or
package.
An Analyzer that uses facts must declare their types:
var Analyzer = &analysis.Analyzer{
Name: "printf",
FactTypes: []analysis.Fact{new(isWrapper)},
...
}
type isWrapper struct{} // => *types.Func f “is a printf wrapper”
The driver program ensures that facts for a pass’s dependencies are
generated before analyzing the package and is responsible for propagating
facts from one package to another, possibly across address spaces.
Consequently, Facts must be serializable. The API requires that drivers
use the gob encoding, an efficient, robust, self-describing binary
protocol. A fact type may implement the GobEncoder/GobDecoder interfaces
if the default encoding is unsuitable. Facts should be stateless.
Because serialized facts may appear within build outputs, the gob encoding
of a fact must be deterministic, to avoid spurious cache misses in
build systems that use content-addressable caches.
The driver makes a single call to the gob encoder for all facts
exported by a given analysis pass, so that the topology of
shared data structures referenced by multiple facts is preserved.
The Pass type has functions to import and export facts,
associated either with an object or with a package:
type Pass struct {
...
ExportObjectFact func(types.Object, Fact)
ImportObjectFact func(types.Object, Fact) bool
ExportPackageFact func(fact Fact)
ImportPackageFact func(*types.Package, Fact) bool
}
An Analyzer may only export facts associated with the current package or
its objects, though it may import facts from any package or object that
is an import dependency of the current package.
Conceptually, ExportObjectFact(obj, fact) inserts fact into a hidden map keyed by
the pair (obj, TypeOf(fact)), and the ImportObjectFact function
retrieves the entry from this map and copies its value into the variable
pointed to by fact. This scheme assumes that the concrete type of fact
is a pointer; this assumption is checked by the Validate function.
See the "printf" analyzer for an example of object facts in action.
Some driver implementations (such as those based on Bazel and Blaze) do
not currently apply analyzers to packages of the standard library.
Therefore, for best results, analyzer authors should not rely on
analysis facts being available for standard packages.
For example, although the printf checker is capable of deducing during
analysis of the log package that log.Printf is a printf wrapper,
this fact is built in to the analyzer so that it correctly checks
calls to log.Printf even when run in a driver that does not apply
it to standard packages. We would like to remove this limitation in future.
# Testing an Analyzer
The analysistest subpackage provides utilities for testing an Analyzer.
In a few lines of code, it is possible to run an analyzer on a package
of testdata files and check that it reported all the expected
diagnostics and facts (and no more). Expectations are expressed using
"// want ..." comments in the input code.
# Standalone commands
Analyzers are provided in the form of packages that a driver program is
expected to import. The vet command imports a set of several analyzers,
but users may wish to define their own analysis commands that perform
additional checks. To simplify the task of creating an analysis command,
either for a single analyzer or for a whole suite, we provide the
singlechecker and multichecker subpackages.
The singlechecker package provides the main function for a command that
runs one analyzer. By convention, each analyzer such as
go/analysis/passes/findcall should be accompanied by a singlechecker-based
command such as go/analysis/passes/findcall/cmd/findcall, defined in its
entirety as:
package main
import (
"golang.org/x/tools/go/analysis/passes/findcall"
"golang.org/x/tools/go/analysis/singlechecker"
)
func main() { singlechecker.Main(findcall.Analyzer) }
A tool that provides multiple analyzers can use multichecker in a
similar way, giving it the list of Analyzers.
*/
package analysis
================================================
FILE: go/analysis/internal/analysisflags/flags.go
================================================
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package analysisflags defines helpers for processing flags (-help,
// -json, -fix, -diff, etc) common to unitchecker and
// {single,multi}checker. It is not intended for broader use.
package analysisflags
import (
"crypto/sha256"
"encoding/gob"
"encoding/json"
"flag"
"fmt"
"io"
"log"
"os"
"strconv"
"golang.org/x/tools/go/analysis"
)
// flags common to all {single,multi,unit}checkers.
var (
JSON = false // -json
Context = -1 // -c=N: if N>0, display offending line plus N lines of context
Fix bool // -fix
Diff bool // -diff
)
// Parse creates a flag for each of the analyzer's flags,
// including (in multi mode) a flag named after the analyzer,
// parses the flags, then filters and returns the list of
// analyzers enabled by flags.
//
// The result is intended to be passed to unitchecker.Run or checker.Run.
// Use in unitchecker.Run will gob.Register all fact types for the returned
// graph of analyzers but of course not the ones only reachable from
// dropped analyzers. To avoid inconsistency about which gob types are
// registered from run to run, Parse itself gob.Registers all the facts
// only reachable from dropped analyzers.
// This is not a particularly elegant API, but this is an internal package.
func Parse(analyzers []*analysis.Analyzer, multi bool) []*analysis.Analyzer {
// Connect each analysis flag to the command line as -analysis.flag.
enabled := make(map[*analysis.Analyzer]*triState)
for _, a := range analyzers {
var prefix string
// Add -NAME flag to enable it.
if multi {
prefix = a.Name + "."
enable := new(triState)
enableUsage := fmt.Sprintf("enable %q analysis", a.Name)
flag.Var(enable, a.Name, enableUsage)
enabled[a] = enable
}
a.Flags.VisitAll(func(f *flag.Flag) {
if !multi && flag.Lookup(f.Name) != nil {
log.Printf("%s flag -%s would conflict with driver; skipping", a.Name, f.Name)
return
}
name := prefix + f.Name
flag.Var(f.Value, name, f.Usage)
})
}
// standard flags: -flags, -V.
printflags := flag.Bool("flags", false, "print analyzer flags in JSON")
addVersionFlag()
// flags common to all checkers
flag.BoolVar(&JSON, "json", JSON, "emit JSON output")
flag.IntVar(&Context, "c", Context, `display offending line with this many lines of context`)
flag.BoolVar(&Fix, "fix", false, "apply all suggested fixes")
flag.BoolVar(&Diff, "diff", false, "with -fix, don't update the files, but print a unified diff")
// Add shims for legacy vet flags to enable existing
// scripts that run vet to continue to work.
_ = flag.Bool("source", false, "no effect (deprecated)")
_ = flag.Bool("v", false, "no effect (deprecated)")
_ = flag.Bool("all", false, "no effect (deprecated)")
_ = flag.String("tags", "", "no effect (deprecated)")
for old, new := range vetLegacyFlags {
newFlag := flag.Lookup(new)
if newFlag != nil && flag.Lookup(old) == nil {
flag.Var(newFlag.Value, old, "deprecated alias for -"+new)
}
}
flag.Parse() // (ExitOnError)
// -flags: print flags so that go vet knows which ones are legitimate.
if *printflags {
printFlags()
os.Exit(0)
}
everything := expand(analyzers)
// If any -NAME flag is true, run only those analyzers. Otherwise,
// if any -NAME flag is false, run all but those analyzers.
if multi {
var hasTrue, hasFalse bool
for _, ts := range enabled {
switch *ts {
case setTrue:
hasTrue = true
case setFalse:
hasFalse = true
}
}
var keep []*analysis.Analyzer
if hasTrue {
for _, a := range analyzers {
if *enabled[a] == setTrue {
keep = append(keep, a)
}
}
analyzers = keep
} else if hasFalse {
for _, a := range analyzers {
if *enabled[a] != setFalse {
keep = append(keep, a)
}
}
analyzers = keep
}
}
// Register fact types of skipped analyzers
// in case we encounter them in imported files.
kept := expand(analyzers)
for a := range everything {
if !kept[a] {
for _, f := range a.FactTypes {
gob.Register(f)
}
}
}
return analyzers
}
func expand(analyzers []*analysis.Analyzer) map[*analysis.Analyzer]bool {
seen := make(map[*analysis.Analyzer]bool)
var visitAll func([]*analysis.Analyzer)
visitAll = func(analyzers []*analysis.Analyzer) {
for _, a := range analyzers {
if !seen[a] {
seen[a] = true
visitAll(a.Requires)
}
}
}
visitAll(analyzers)
return seen
}
func printFlags() {
type jsonFlag struct {
Name string
Bool bool
Usage string
}
var flags []jsonFlag = nil
flag.VisitAll(func(f *flag.Flag) {
// Don't report {single,multi}checker debugging
// flags or fix as these have no effect on unitchecker
// (as invoked by 'go vet').
switch f.Name {
case "debug", "cpuprofile", "memprofile", "trace", "fix":
return
}
b, ok := f.Value.(interface{ IsBoolFlag() bool })
isBool := ok && b.IsBoolFlag()
flags = append(flags, jsonFlag{f.Name, isBool, f.Usage})
})
data, err := json.MarshalIndent(flags, "", "\t")
if err != nil {
log.Fatal(err)
}
os.Stdout.Write(data)
}
// addVersionFlag registers a -V flag that, if set,
// prints the executable version and exits 0.
//
// If the -V flag already exists — for example, because it was already
// registered by a call to cmd/internal/objabi.AddVersionFlag — then
// addVersionFlag does nothing.
func addVersionFlag() {
if flag.Lookup("V") == nil {
flag.Var(versionFlag{}, "V", "print version and exit")
}
}
// versionFlag minimally complies with the -V protocol required by "go vet".
type versionFlag struct{}
func (versionFlag) IsBoolFlag() bool { return true }
func (versionFlag) Get() any { return nil }
func (versionFlag) String() string { return "" }
func (versionFlag) Set(s string) error {
if s != "full" {
log.Fatalf("unsupported flag value: -V=%s (use -V=full)", s)
}
// This replicates the minimal subset of
// cmd/internal/objabi.AddVersionFlag, which is private to the
// go tool yet forms part of our command-line interface.
// TODO(adonovan): clarify the contract.
// Print the tool version so the build system can track changes.
// Formats:
// $progname version devel ... buildID=...
// $progname version go1.9.1
progname, err := os.Executable()
if err != nil {
return err
}
f, err := os.Open(progname)
if err != nil {
log.Fatal(err)
}
h := sha256.New()
if _, err := io.Copy(h, f); err != nil {
log.Fatal(err)
}
f.Close()
fmt.Printf("%s version devel comments-go-here buildID=%02x\n",
progname, string(h.Sum(nil)))
os.Exit(0)
return nil
}
// A triState is a boolean that knows whether
// it has been set to either true or false.
// It is used to identify whether a flag appears;
// the standard boolean flag cannot
// distinguish missing from unset.
// It also satisfies flag.Value.
type triState int
const (
unset triState = iota
setTrue
setFalse
)
// triState implements flag.Value, flag.Getter, and flag.boolFlag.
// They work like boolean flags: we can say vet -printf as well as vet -printf=true
func (ts *triState) Get() any {
return *ts == setTrue
}
func (ts *triState) Set(value string) error {
b, err := strconv.ParseBool(value)
if err != nil {
// This error message looks poor but package "flag" adds
// "invalid boolean value %q for -NAME: %s"
return fmt.Errorf("want true or false")
}
if b {
*ts = setTrue
} else {
*ts = setFalse
}
return nil
}
func (ts *triState) String() string {
switch *ts {
case unset:
return "true"
case setTrue:
return "true"
case setFalse:
return "false"
}
panic("not reached")
}
func (ts triState) IsBoolFlag() bool {
return true
}
// Legacy flag support
// vetLegacyFlags maps flags used by legacy vet to their corresponding
// new names. The old names will continue to work.
var vetLegacyFlags = map[string]string{
// Analyzer name changes
"bool": "bools",
"buildtags": "buildtag",
"methods": "stdmethods",
"rangeloops": "loopclosure",
// Analyzer flags
"compositewhitelist": "composites.whitelist",
"printfuncs": "printf.funcs",
"shadowstrict": "shadow.strict",
"unusedfuncs": "unusedresult.funcs",
"unusedstringmethods": "unusedresult.stringmethods",
}
================================================
FILE: go/analysis/internal/analysisflags/flags_test.go
================================================
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package analysisflags_test
import (
"fmt"
"os"
"os/exec"
"runtime"
"strings"
"testing"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/internal/analysisflags"
)
func main() {
fmt.Println(analysisflags.Parse([]*analysis.Analyzer{
{Name: "a1", Doc: "a1"},
{Name: "a2", Doc: "a2"},
{Name: "a3", Doc: "a3"},
}, true))
os.Exit(0)
}
// This test fork/execs the main function above.
func TestExec(t *testing.T) {
if runtime.GOOS != "linux" {
t.Skipf("skipping fork/exec test on this platform")
}
progname := os.Args[0]
if os.Getenv("ANALYSISFLAGS_CHILD") == "1" {
// child process
os.Args = strings.Fields(progname + " " + os.Getenv("FLAGS"))
main()
panic("unreachable")
}
for _, test := range []struct {
flags string
want string // output should contain want
}{
{"", "[a1 a2 a3]"},
{"-a1=0", "[a2 a3]"},
{"-a1=1", "[a1]"},
{"-a1", "[a1]"},
{"-a1=1 -a3=1", "[a1 a3]"},
{"-a1=1 -a3=0", "[a1]"},
{"-V=full", "analysisflags.test version devel"},
} {
cmd := exec.Command(progname, "-test.run=TestExec")
cmd.Env = append(os.Environ(), "ANALYSISFLAGS_CHILD=1", "FLAGS="+test.flags)
output, err := cmd.CombinedOutput()
if err != nil {
t.Fatalf("exec failed: %v; output=<<%s>>", err, output)
}
got := strings.TrimSpace(string(output))
if !strings.Contains(got, test.want) {
t.Errorf("got %q, does not contain %q", got, test.want)
}
}
}
================================================
FILE: go/analysis/internal/analysisflags/help.go
================================================
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package analysisflags
import (
"flag"
"fmt"
"log"
"os"
"sort"
"strings"
"golang.org/x/tools/go/analysis"
)
const help = `PROGNAME is a tool for static analysis of Go programs.
PROGNAME examines Go source code and reports diagnostics for
suspicious constructs or opportunities for improvement.
Diagnostics may include suggested fixes.
An example of a suspicious construct is a Printf call whose arguments
do not align with the format string. Analyzers may use heuristics that
do not guarantee all reports are genuine problems, but can find
mistakes not caught by the compiler.
An example of an opportunity for improvement is a loop over
strings.Split(doc, "\n"), which may be replaced by a loop over the
strings.SplitSeq iterator, avoiding an array allocation.
Diagnostics in such cases may report non-problems,
but should carry fixes that may be safely applied.
For analyzers of the first kind, use "go vet -vettool=PROGRAM"
to run the tool and report diagnostics.
For analyzers of the second kind, use "go fix -fixtool=PROGRAM"
to run the tool and apply the fixes it suggests.
`
// Help implements the help subcommand for a multichecker or unitchecker
// style command. The optional args specify the analyzers to describe.
// Help calls log.Fatal if no such analyzer exists.
func Help(progname string, analyzers []*analysis.Analyzer, args []string) {
// No args: show summary of all analyzers.
if len(args) == 0 {
fmt.Println(strings.ReplaceAll(help, "PROGNAME", progname))
fmt.Println("Registered analyzers:")
fmt.Println()
sort.Slice(analyzers, func(i, j int) bool {
return analyzers[i].Name < analyzers[j].Name
})
for _, a := range analyzers {
title := strings.Split(a.Doc, "\n\n")[0]
fmt.Printf(" %-12s %s\n", a.Name, title)
}
fmt.Println("\nBy default all analyzers are run.")
fmt.Println("To select specific analyzers, use the -NAME flag for each one,")
fmt.Println(" or -NAME=false to run all analyzers not explicitly disabled.")
// Show only the core command-line flags.
fmt.Println("\nCore flags:")
fmt.Println()
fs := flag.NewFlagSet("", flag.ExitOnError)
flag.VisitAll(func(f *flag.Flag) {
if !strings.Contains(f.Name, ".") {
fs.Var(f.Value, f.Name, f.Usage)
}
})
fs.SetOutput(os.Stdout)
fs.PrintDefaults()
fmt.Printf("\nTo see details and flags of a specific analyzer, run '%s help name'.\n", progname)
return
}
// Show help on specific analyzer(s).
outer:
for _, arg := range args {
for _, a := range analyzers {
if a.Name == arg {
paras := strings.Split(a.Doc, "\n\n")
title := paras[0]
fmt.Printf("%s: %s\n", a.Name, title)
// Show only the flags relating to this analysis,
// properly prefixed.
first := true
fs := flag.NewFlagSet(a.Name, flag.ExitOnError)
a.Flags.VisitAll(func(f *flag.Flag) {
if first {
first = false
fmt.Println("\nAnalyzer flags:")
fmt.Println()
}
fs.Var(f.Value, a.Name+"."+f.Name, f.Usage)
})
fs.SetOutput(os.Stdout)
fs.PrintDefaults()
if len(paras) > 1 {
fmt.Printf("\n%s\n", strings.Join(paras[1:], "\n\n"))
}
continue outer
}
}
log.Fatalf("Analyzer %q not registered", arg)
}
}
================================================
FILE: go/analysis/internal/checker/checker.go
================================================
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package internal/checker defines various implementation helpers for
// the singlechecker and multichecker packages, which provide the
// complete main function for an analysis driver executable
// based on go/packages.
//
// (Note: it is not used by the public 'checker' package, since the
// latter provides a set of pure functions for use as building blocks.)
package checker
// TODO(adonovan): publish the JSON schema in go/analysis or analysisjson.
import (
"flag"
"fmt"
"io"
"log"
"os"
"runtime"
"runtime/pprof"
"runtime/trace"
"sort"
"strings"
"time"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/checker"
"golang.org/x/tools/go/analysis/internal"
"golang.org/x/tools/go/analysis/internal/analysisflags"
"golang.org/x/tools/go/packages"
"golang.org/x/tools/internal/analysis/driverutil"
)
var (
// Debug is a set of single-letter flags:
//
// f show [f]acts as they are created
// p disable [p]arallel execution of analyzers
// s do additional [s]anity checks on fact types and serialization
// t show [t]iming info (NB: use 'p' flag to avoid GC/scheduler noise)
// v show [v]erbose logging
//
Debug = ""
// Log files for optional performance tracing.
CPUProfile, MemProfile, Trace string
// IncludeTests indicates whether test files should be analyzed too.
IncludeTests = true
)
// RegisterFlags registers command-line flags used by the analysis driver.
func RegisterFlags() {
// When adding flags here, remember to update
// the list of suppressed flags in analysisflags.
flag.StringVar(&Debug, "debug", Debug, `debug flags, any subset of "fpstv"`)
flag.StringVar(&CPUProfile, "cpuprofile", "", "write CPU profile to this file")
flag.StringVar(&MemProfile, "memprofile", "", "write memory profile to this file")
flag.StringVar(&Trace, "trace", "", "write trace log to this file")
flag.BoolVar(&IncludeTests, "test", IncludeTests, "indicates whether test files should be analyzed, too")
}
// Run loads the packages specified by args using go/packages,
// then applies the specified analyzers to them.
// Analysis flags must already have been set.
// Analyzers must be valid according to [analysis.Validate].
// It provides most of the logic for the main functions of both the
// singlechecker and the multi-analysis commands.
// It returns the appropriate exit code.
//
// TODO(adonovan): tests should not call this function directly.
// Fiddling with global variables (flags such as [analysisflags.Fix])
// is error-prone and hostile to parallelism. Instead, use unit tests
// of the actual units (e.g. checker.Analyze) and integration tests
// (e.g. TestScript) of whole executables.
func Run(args []string, analyzers []*analysis.Analyzer) (exitcode int) {
// Instead of returning a code directly,
// call this function to monotonically increase the exit code.
// This allows us to keep going in the face of some errors
// without having to remember what code to return.
//
// TODO(adonovan): interpreting exit codes is like reading tea-leaves.
// Instead of wasting effort trying to encode a multidimensional result
// into 7 bits we should just emit structured JSON output, and
// an exit code of 0 or 1 for success or failure.
exitAtLeast := func(code int) {
exitcode = max(code, exitcode)
}
// Since analysisflags is linked in (for {single,multi}checker),
// the -v flag is registered for complex legacy reasons
// related to cmd/vet CLI.
// Treat it as an undocumented alias for -debug=v.
if v := flag.CommandLine.Lookup("v"); v != nil &&
v.Value.(flag.Getter).Get() == true &&
!strings.Contains(Debug, "v") {
Debug += "v"
}
if CPUProfile != "" {
f, err := os.Create(CPUProfile)
if err != nil {
log.Fatal(err)
}
if err := pprof.StartCPUProfile(f); err != nil {
log.Fatal(err)
}
// NB: profile won't be written in case of error.
defer pprof.StopCPUProfile()
}
if Trace != "" {
f, err := os.Create(Trace)
if err != nil {
log.Fatal(err)
}
if err := trace.Start(f); err != nil {
log.Fatal(err)
}
// NB: trace log won't be written in case of error.
defer func() {
trace.Stop()
log.Printf("To view the trace, run:\n$ go tool trace view %s", Trace)
}()
}
if MemProfile != "" {
f, err := os.Create(MemProfile)
if err != nil {
log.Fatal(err)
}
// NB: memprofile won't be written in case of error.
defer func() {
runtime.GC() // get up-to-date statistics
if err := pprof.WriteHeapProfile(f); err != nil {
log.Fatalf("Writing memory profile: %v", err)
}
f.Close()
}()
}
// Load the packages.
if dbg('v') {
log.SetPrefix("")
log.SetFlags(log.Lmicroseconds) // display timing
log.Printf("load %s", args)
}
// Optimization: if the selected analyzers don't produce/consume
// facts, we need source only for the initial packages.
allSyntax := needFacts(analyzers)
initial, err := load(args, allSyntax)
if err != nil {
log.Print(err)
exitAtLeast(1)
return
}
// Print package and module errors regardless of RunDespiteErrors.
// Do not exit if there are errors, yet.
if n := packages.PrintErrors(initial); n > 0 {
exitAtLeast(1)
}
var factLog io.Writer
if dbg('f') {
factLog = os.Stderr
}
// Run the analysis.
opts := &checker.Options{
SanityCheck: dbg('s'),
Sequential: dbg('p'),
FactLog: factLog,
}
if dbg('v') {
log.Printf("building graph of analysis passes")
}
graph, err := checker.Analyze(analyzers, initial, opts)
if err != nil {
log.Print(err)
exitAtLeast(1)
return
}
// Don't print the diagnostics,
// but apply all fixes from the root actions.
if analysisflags.Fix {
fixActions := make([]driverutil.FixAction, len(graph.Roots))
for i, act := range graph.Roots {
if pass := internal.ActionPass(act); pass != nil {
fixActions[i] = driverutil.FixAction{
Name: act.String(),
Pkg: act.Package.Types,
Files: act.Package.Syntax,
FileSet: act.Package.Fset,
ReadFileFunc: pass.ReadFile,
Diagnostics: act.Diagnostics,
}
}
}
write := func(filename string, content []byte) error {
return os.WriteFile(filename, content, 0644)
}
if err := driverutil.ApplyFixes(fixActions, write, analysisflags.Diff, dbg('v')); err != nil {
// Fail when applying fixes failed.
log.Print(err)
exitAtLeast(1)
return
}
// Don't proceed to print text/JSON,
// and don't report an error
// just because there were diagnostics.
return
}
// Print the results. If !RunDespiteErrors and there
// are errors in the packages, this will have 0 exit
// code. Otherwise, we prefer to return exit code
// indicating diagnostics.
exitAtLeast(printDiagnostics(graph))
return
}
// printDiagnostics prints diagnostics in text or JSON form
// and returns the appropriate exit code.
func printDiagnostics(graph *checker.Graph) (exitcode int) {
// Keep consistent with analogous logic in
// processResults in ../../unitchecker/unitchecker.go.
// Print the results.
// With -json, the exit code is always zero.
if analysisflags.JSON {
if err := graph.PrintJSON(os.Stdout); err != nil {
return 1
}
} else {
if err := graph.PrintText(os.Stderr, analysisflags.Context); err != nil {
return 1
}
// Compute the exit code.
var numErrors, rootDiags int
for act := range graph.All() {
if act.Err != nil {
numErrors++
} else if act.IsRoot {
rootDiags += len(act.Diagnostics)
}
}
if numErrors > 0 {
exitcode = 1 // analysis failed, at least partially
} else if rootDiags > 0 {
exitcode = 3 // successfully produced diagnostics
}
}
// Print timing info.
if dbg('t') {
if !dbg('p') {
log.Println("Warning: times are mostly GC/scheduler noise; use -debug=tp to disable parallelism")
}
var list []*checker.Action
var total time.Duration
for act := range graph.All() {
list = append(list, act)
total += act.Duration
}
// Print actions accounting for 90% of the total.
sort.Slice(list, func(i, j int) bool {
return list[i].Duration > list[j].Duration
})
var sum time.Duration
for _, act := range list {
fmt.Fprintf(os.Stderr, "%s\t%s\n", act.Duration, act)
sum += act.Duration
if sum >= total*9/10 {
break
}
}
if total > sum {
fmt.Fprintf(os.Stderr, "%s\tall others\n", total-sum)
}
}
return exitcode
}
// load loads the initial packages. Returns only top-level loading
// errors. Does not consider errors in packages.
func load(patterns []string, allSyntax bool) ([]*packages.Package, error) {
mode := packages.LoadSyntax
if allSyntax {
mode = packages.LoadAllSyntax
}
mode |= packages.NeedModule
conf := packages.Config{
Mode: mode,
// Ensure that child process inherits correct alias of PWD.
// (See discussion at Dir field of [exec.Command].)
// However, this currently breaks some tests.
// TODO(adonovan): Investigate.
//
// Dir: os.Getenv("PWD"),
Tests: IncludeTests,
}
initial, err := packages.Load(&conf, patterns...)
if err == nil && len(initial) == 0 {
err = fmt.Errorf("%s matched no packages", strings.Join(patterns, " "))
}
return initial, err
}
// needFacts reports whether any analysis required by the specified set
// needs facts. If so, we must load the entire program from source.
func needFacts(analyzers []*analysis.Analyzer) bool {
seen := make(map[*analysis.Analyzer]bool)
var q []*analysis.Analyzer // for BFS
q = append(q, analyzers...)
for len(q) > 0 {
a := q[0]
q = q[1:]
if !seen[a] {
seen[a] = true
if len(a.FactTypes) > 0 {
return true
}
q = append(q, a.Requires...)
}
}
return false
}
func dbg(b byte) bool { return strings.IndexByte(Debug, b) >= 0 }
================================================
FILE: go/analysis/internal/checker/checker_test.go
================================================
// Copyright 2019 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package checker_test
import (
"os"
"path/filepath"
"reflect"
"strings"
"testing"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/analysistest"
"golang.org/x/tools/go/analysis/internal/analysisflags"
"golang.org/x/tools/go/analysis/internal/checker"
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/internal/testenv"
"golang.org/x/tools/internal/testfiles"
"golang.org/x/tools/txtar"
)
func TestApplyFixes(t *testing.T) {
testenv.NeedsGoPackages(t)
testenv.RedirectStderr(t) // associated checker.Run output with this test
files := map[string]string{
"rename/test.go": `package rename
func Foo() {
bar := 12
_ = bar
}
// the end
`}
want := `package rename
func Foo() {
baz := 12
_ = baz
}
// the end
`
testdata, cleanup, err := analysistest.WriteFiles(files)
if err != nil {
t.Fatal(err)
}
path := filepath.Join(testdata, "src/rename/test.go")
analysisflags.Fix = true
checker.Run([]string{"file=" + path}, []*analysis.Analyzer{renameAnalyzer})
analysisflags.Fix = false
contents, err := os.ReadFile(path)
if err != nil {
t.Fatal(err)
}
got := string(contents)
if got != want {
t.Errorf("contents of rewritten file\ngot: %s\nwant: %s", got, want)
}
defer cleanup()
}
func TestRunDespiteErrors(t *testing.T) {
testenv.NeedsGoPackages(t)
testenv.RedirectStderr(t) // associate checker.Run output with this test
files := map[string]string{
"rderr/test.go": `package rderr
// Foo deliberately has a type error
func Foo(s string) int {
return s + 1
}
`,
"cperr/test.go": `package copyerr
import "sync"
func bar() { } // for renameAnalyzer
type T struct{ mu sync.Mutex }
type T1 struct{ t *T }
func NewT1() *T1 { return &T1{T} }
`,
}
testdata, cleanup, err := analysistest.WriteFiles(files)
if err != nil {
t.Fatal(err)
}
defer cleanup()
rderrFile := "file=" + filepath.Join(testdata, "src/rderr/test.go")
cperrFile := "file=" + filepath.Join(testdata, "src/cperr/test.go")
// A no-op analyzer that should finish regardless of
// parse or type errors in the code.
noop := &analysis.Analyzer{
Name: "noop",
Doc: "noop",
Requires: []*analysis.Analyzer{inspect.Analyzer},
Run: func(pass *analysis.Pass) (any, error) {
return nil, nil
},
RunDespiteErrors: true,
}
// A no-op analyzer, with facts, that should finish
// regardless of parse or type errors in the code.
noopWithFact := &analysis.Analyzer{
Name: "noopfact",
Doc: "noopfact",
Requires: []*analysis.Analyzer{inspect.Analyzer},
Run: func(pass *analysis.Pass) (any, error) {
return nil, nil
},
RunDespiteErrors: true,
FactTypes: []analysis.Fact{&EmptyFact{}},
}
for _, test := range []struct {
name string
pattern []string
analyzers []*analysis.Analyzer
code int
}{
// parse/type errors
{name: "skip-error", pattern: []string{rderrFile}, analyzers: []*analysis.Analyzer{renameAnalyzer}, code: 1},
// RunDespiteErrors allows a driver to run an Analyzer even after parse/type errors.
//
// The noop analyzer doesn't use facts, so the driver loads only the root
// package from source. For the rest, it asks 'go list' for export data,
// which fails because the compiler encounters the type error. Since the
// errors come from 'go list', the driver doesn't run the analyzer.
{name: "despite-error", pattern: []string{rderrFile}, analyzers: []*analysis.Analyzer{noop}, code: exitCodeFailed},
// The noopfact analyzer does use facts, so the driver loads source for
// all dependencies, does type checking itself, recognizes the error as a
// type error, and runs the analyzer.
{name: "despite-error-fact", pattern: []string{rderrFile}, analyzers: []*analysis.Analyzer{noopWithFact}, code: exitCodeFailed},
// combination of parse/type errors and no errors
{name: "despite-error-and-no-error", pattern: []string{rderrFile, "sort"}, analyzers: []*analysis.Analyzer{renameAnalyzer, noop}, code: exitCodeFailed},
// non-existing package error
{name: "no-package", pattern: []string{"xyz"}, analyzers: []*analysis.Analyzer{renameAnalyzer}, code: exitCodeFailed},
{name: "no-package-despite-error", pattern: []string{"abc"}, analyzers: []*analysis.Analyzer{noop}, code: exitCodeFailed},
{name: "no-multi-package-despite-error", pattern: []string{"xyz", "abc"}, analyzers: []*analysis.Analyzer{noop}, code: exitCodeFailed},
// combination of type/parsing and different errors
{name: "different-errors", pattern: []string{rderrFile, "xyz"}, analyzers: []*analysis.Analyzer{renameAnalyzer, noop}, code: exitCodeFailed},
// non existing dir error
{name: "no-match-dir", pattern: []string{"file=non/existing/dir"}, analyzers: []*analysis.Analyzer{renameAnalyzer, noop}, code: exitCodeFailed},
// no errors
{name: "no-errors", pattern: []string{"sort"}, analyzers: []*analysis.Analyzer{renameAnalyzer, noop}, code: exitCodeSuccess},
// duplicate list error with no findings
{name: "list-error", pattern: []string{cperrFile}, analyzers: []*analysis.Analyzer{noop}, code: exitCodeFailed},
// duplicate list errors with findings (issue #67790)
{name: "list-error-findings", pattern: []string{cperrFile}, analyzers: []*analysis.Analyzer{renameAnalyzer}, code: exitCodeDiagnostics},
} {
t.Run(test.name, func(t *testing.T) {
if got := checker.Run(test.pattern, test.analyzers); got != test.code {
t.Errorf("got incorrect exit code %d for test %s; want %d", got, test.name, test.code)
}
})
}
}
type EmptyFact struct{}
func (f *EmptyFact) AFact() {}
func TestURL(t *testing.T) {
// TestURL test that URLs get forwarded to diagnostics by internal/checker.
testenv.NeedsGoPackages(t)
files := map[string]string{
"p/test.go": `package p // want "package name is p"`,
}
pkgname := &analysis.Analyzer{
Name: "pkgname",
Doc: "trivial analyzer that reports package names",
URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/internal/checker",
Run: func(p *analysis.Pass) (any, error) {
for _, f := range p.Files {
p.ReportRangef(f.Name, "package name is %s", f.Name.Name)
}
return nil, nil
},
}
testdata, cleanup, err := analysistest.WriteFiles(files)
if err != nil {
t.Fatal(err)
}
defer cleanup()
path := filepath.Join(testdata, "src/p/test.go")
results := analysistest.Run(t, testdata, pkgname, "file="+path)
var urls []string
for _, r := range results {
for _, d := range r.Diagnostics {
urls = append(urls, d.URL)
}
}
want := []string{"https://pkg.go.dev/golang.org/x/tools/go/analysis/internal/checker"}
if !reflect.DeepEqual(urls, want) {
t.Errorf("Expected Diagnostics.URLs %v. got %v", want, urls)
}
}
// TestPassReadFile exercises the Pass.ReadFile function.
func TestPassReadFile(t *testing.T) {
cwd, _ := os.Getwd()
const src = `
-- go.mod --
module example.com
-- p/file.go --
package p
-- p/ignored.go --
//go:build darwin && mips64
package p
hello from ignored
-- p/other.s --
hello from other
`
// Expand archive into tmp tree.
fs, err := txtar.FS(txtar.Parse([]byte(src)))
if err != nil {
t.Fatal(err)
}
tmpdir := testfiles.CopyToTmp(t, fs)
ran := false
a := &analysis.Analyzer{
Name: "a",
Requires: []*analysis.Analyzer{inspect.Analyzer},
Doc: "doc",
Run: func(pass *analysis.Pass) (any, error) {
if len(pass.OtherFiles)+len(pass.IgnoredFiles) == 0 {
t.Errorf("OtherFiles and IgnoredFiles are empty")
return nil, nil
}
for _, test := range []struct {
filename string
want string // substring of file content or error message
}{
{
pass.OtherFiles[0], // [other.s]
"hello from other",
},
{
pass.IgnoredFiles[0], // [ignored.go]
"hello from ignored",
},
{
"nonesuch",
"nonesuch is not among OtherFiles, ", // etc
},
{
filepath.Join(cwd, "checker_test.go"),
"checker_test.go is not among OtherFiles, ", // etc
},
} {
content, err := pass.ReadFile(test.filename)
var got string
if err != nil {
got = err.Error()
} else {
got = string(content)
if len(got) > 100 {
got = got[:100] + "..."
}
}
if !strings.Contains(got, test.want) {
t.Errorf("Pass.ReadFile(%q) did not contain %q; got:\n%s",
test.filename, test.want, got)
}
}
ran = true
return nil, nil
},
}
analysistest.Run(t, tmpdir, a, "example.com/p")
if !ran {
t.Error("analyzer did not run")
}
// TODO(adonovan): test that fixes are applied to the
// pass.ReadFile virtual file tree.
}
================================================
FILE: go/analysis/internal/checker/fix_test.go
================================================
// Copyright 2022 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package checker_test
import (
"bytes"
"flag"
"fmt"
"go/ast"
"go/token"
"log"
"os"
"os/exec"
"path/filepath"
"regexp"
"runtime"
"slices"
"strings"
"testing"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/checker"
"golang.org/x/tools/go/analysis/multichecker"
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/ast/inspector"
"golang.org/x/tools/go/packages"
"golang.org/x/tools/internal/diff"
"golang.org/x/tools/internal/expect"
"golang.org/x/tools/internal/testenv"
"golang.org/x/tools/internal/testfiles"
"golang.org/x/tools/txtar"
)
func TestMain(m *testing.M) {
// If the CHECKER_TEST_CHILD environment variable is set,
// this process should behave like a multichecker.
// Analyzers are selected by flags.
if _, ok := os.LookupEnv("CHECKER_TEST_CHILD"); ok {
multichecker.Main(
markerAnalyzer,
noendAnalyzer,
renameAnalyzer,
relatedAnalyzer,
)
panic("unreachable")
}
// ordinary test
flag.Parse()
os.Exit(m.Run())
}
const (
exitCodeSuccess = 0 // success (no diagnostics, or successful -fix)
exitCodeFailed = 1 // analysis failed to run
exitCodeDiagnostics = 3 // diagnostics were reported (and no -fix)
)
// TestReportInvalidDiagnostic tests that a call to pass.Report with
// certain kind of invalid diagnostic (e.g. conflicting fixes)
// promptly results in a panic.
func TestReportInvalidDiagnostic(t *testing.T) {
testenv.NeedsGoPackages(t)
// Load the errors package.
cfg := &packages.Config{Mode: packages.LoadAllSyntax}
initial, err := packages.Load(cfg, "errors")
if err != nil {
t.Fatal(err)
}
for _, test := range []struct {
name string
want string
diag func(pos token.Pos) analysis.Diagnostic
}{
// Diagnostic has two alternative fixes with the same Message.
{
"duplicate message",
`analyzer "a" suggests two fixes with same Message \(fix\)`,
func(pos token.Pos) analysis.Diagnostic {
return analysis.Diagnostic{
Pos: pos,
Message: "oops",
SuggestedFixes: []analysis.SuggestedFix{
{Message: "fix"},
{Message: "fix"},
},
}
},
},
// TextEdit has invalid Pos.
{
"bad Pos",
`analyzer "a" suggests invalid fix .*: no token.File for TextEdit.Pos .0.`,
func(pos token.Pos) analysis.Diagnostic {
return analysis.Diagnostic{
Pos: pos,
Message: "oops",
SuggestedFixes: []analysis.SuggestedFix{
{
Message: "fix",
TextEdits: []analysis.TextEdit{{}},
},
},
}
},
},
// TextEdit has invalid End.
{
"End < Pos",
`analyzer "a" suggests invalid fix .*: TextEdit.Pos .* > TextEdit.End .*`,
func(pos token.Pos) analysis.Diagnostic {
return analysis.Diagnostic{
Pos: pos,
Message: "oops",
SuggestedFixes: []analysis.SuggestedFix{
{
Message: "fix",
TextEdits: []analysis.TextEdit{{
Pos: pos + 2,
End: pos,
}},
},
},
}
},
},
// Two TextEdits overlap.
{
"overlapping edits",
`analyzer "a" suggests invalid fix .*: overlapping edits to .*errors.go \(1:1-1:3 and 1:2-1:4\)`,
func(pos token.Pos) analysis.Diagnostic {
return analysis.Diagnostic{
Pos: pos,
Message: "oops",
SuggestedFixes: []analysis.SuggestedFix{
{
Message: "fix",
TextEdits: []analysis.TextEdit{
{Pos: pos, End: pos + 2},
{Pos: pos + 1, End: pos + 3},
},
},
},
}
},
},
} {
t.Run(test.name, func(t *testing.T) {
reached := false
a := &analysis.Analyzer{Name: "a", Doc: "doc", Run: func(pass *analysis.Pass) (any, error) {
reached = true
panics(t, test.want, func() {
pos := pass.Files[0].FileStart
pass.Report(test.diag(pos))
})
return nil, nil
}}
if _, err := checker.Analyze([]*analysis.Analyzer{a}, initial, &checker.Options{}); err != nil {
t.Fatalf("Analyze failed: %v", err)
}
if !reached {
t.Error("analyzer was never invoked")
}
})
}
}
// TestScript runs script-driven tests in testdata/*.txt.
// Each file is a txtar archive, expanded to a temporary directory.
//
// The comment section of the archive is a script, with the following
// commands:
//
// # comment
// ignored
// blank line
// ignored
// skip k=v...
// Skip the test if any k=v string is a substring of the string
// "GOOS=darwin GOARCH=arm64" appropriate to the current build.
// checker args...
// Run the checker command with the specified space-separated
// arguments; this fork+execs the [TestMain] function above.
// If the archive has a "stdout" section, its contents must
// match the stdout output of the checker command.
// Do NOT use this for testing -diff: tests should not
// rely on the particulars of the diff algorithm.
// exit int
// Assert that previous checker command had this exit code.
// stderr regexp
// Assert that stderr output from previous checker run matches this pattern.
//
// The script must include at least one 'checker' command.
func TestScript(t *testing.T) {
testenv.NeedsExec(t)
testenv.NeedsGoPackages(t)
txtfiles, err := filepath.Glob("testdata/*.txt")
if err != nil {
t.Fatal(err)
}
for _, txtfile := range txtfiles {
t.Run(txtfile, func(t *testing.T) {
t.Parallel()
// Expand archive into tmp tree.
ar, err := txtar.ParseFile(txtfile)
if err != nil {
t.Fatal(err)
}
fs, err := txtar.FS(ar)
if err != nil {
t.Fatal(err)
}
dir := testfiles.CopyToTmp(t, fs)
// Parse txtar comment as a script.
const noExitCode = -999
var (
// state variables operated on by script
lastExitCode = noExitCode
lastStderr string
)
for i, line := range strings.Split(string(ar.Comment), "\n") {
line = strings.TrimSpace(line)
if line == "" || line[0] == '#' {
continue // skip blanks and comments
}
command, rest, _ := strings.Cut(line, " ")
prefix := fmt.Sprintf("%s:%d: %s", txtfile, i+1, command) // for error messages
switch command {
case "checker":
cmd := exec.Command(os.Args[0], strings.Fields(rest)...)
cmd.Dir = dir
cmd.Stdout = new(strings.Builder)
cmd.Stderr = new(strings.Builder)
cmd.Env = append(os.Environ(), "CHECKER_TEST_CHILD=1", "GOPROXY=off")
if err := cmd.Run(); err != nil {
if err, ok := err.(*exec.ExitError); ok {
lastExitCode = err.ExitCode()
// fall through
} else {
t.Fatalf("%s: failed to execute checker: %v (%s)", prefix, err, cmd)
}
} else {
lastExitCode = 0 // success
}
// Eliminate nondeterministic strings from the output.
clean := func(x any) string {
s := fmt.Sprint(x)
pwd, _ := os.Getwd()
if realDir, err := filepath.EvalSymlinks(dir); err == nil {
// Work around checker's packages.Load failing to
// set Config.Dir to dir, causing the filenames
// of loaded packages not to be a subdir of dir.
s = strings.ReplaceAll(s, realDir, dir)
}
s = strings.ReplaceAll(s, dir, string(os.PathSeparator)+"TMP")
s = strings.ReplaceAll(s, pwd, string(os.PathSeparator)+"PWD")
s = strings.ReplaceAll(s, cmd.Path, filepath.Base(cmd.Path))
return s
}
lastStderr = clean(cmd.Stderr)
stdout := clean(cmd.Stdout)
// Detect bad markers out of band:
// though they cause a non-zero exit,
// that may be expected.
if strings.Contains(lastStderr, badMarker) {
t.Errorf("marker analyzer encountered errors; stderr=%s", lastStderr)
}
// debugging
if false {
t.Logf("%s: $ %s\nstdout:\n%s\nstderr:\n%s", prefix, clean(cmd), stdout, lastStderr)
}
// Keep error reporting logic below consistent with
// applyDiffsAndCompare in ../../analysistest/analysistest.go!
unified := func(xlabel, ylabel string, x, y []byte) string {
x = append(slices.Clip(bytes.TrimSpace(x)), '\n')
y = append(slices.Clip(bytes.TrimSpace(y)), '\n')
return diff.Unified(xlabel, ylabel, string(x), string(y))
}
// Check stdout, if there's a section of that name.
//
// Do not use this for testing -diff! It exposes tests to the
// internals of our (often suboptimal) diff algorithm.
// Instead, use the want/ mechanism.
if f := section(ar, "stdout"); f != nil {
got, want := []byte(stdout), f.Data
if diff := unified("got", "want", got, want); diff != "" {
t.Errorf("%s: unexpected stdout: -- got --\n%s-- want --\n%s-- diff --\n%s",
prefix,
got, want, diff)
}
}
for _, f := range ar.Files {
// For each file named want/X, assert that the
// current content of X now equals want/X.
if filename, ok := strings.CutPrefix(f.Name, "want/"); ok {
fixed, err := os.ReadFile(filepath.Join(dir, filename))
if err != nil {
t.Errorf("reading %s: %v", filename, err)
continue
}
var original []byte
if f := section(ar, filename); f != nil {
original = f.Data
}
want := f.Data
if diff := unified(filename+" (fixed)", filename+" (want)", fixed, want); diff != "" {
t.Errorf("%s: unexpected %s content:\n"+
"-- original --\n%s\n"+
"-- fixed --\n%s\n"+
"-- want --\n%s\n"+
"-- diff original fixed --\n%s\n"+
"-- diff fixed want --\n%s",
prefix, filename,
original,
fixed,
want,
unified(filename+" (original)", filename+" (fixed)", original, fixed),
diff)
}
}
}
case "skip":
config := fmt.Sprintf("GOOS=%s GOARCH=%s", runtime.GOOS, runtime.GOARCH)
for word := range strings.FieldsSeq(rest) {
if strings.Contains(config, word) {
t.Skip(word)
}
}
case "exit":
if lastExitCode == noExitCode {
t.Fatalf("%s: no prior 'checker' command", prefix)
}
var want int
if _, err := fmt.Sscanf(rest, "%d", &want); err != nil {
t.Fatalf("%s: requires one numeric operand", prefix)
}
if want != lastExitCode {
// plan9 ExitCode() currently only returns 0 for success or 1 for failure
if !(runtime.GOOS == "plan9" && want != exitCodeSuccess && lastExitCode != exitCodeSuccess) {
t.Errorf("%s: exit code was %d, want %d", prefix, lastExitCode, want)
}
}
case "stderr":
if lastExitCode == noExitCode {
t.Fatalf("%s: no prior 'checker' command", prefix)
}
if matched, err := regexp.MatchString(rest, lastStderr); err != nil {
t.Fatalf("%s: invalid regexp: %v", prefix, err)
} else if !matched {
t.Errorf("%s: output didn't match pattern %q:\n%s", prefix, rest, lastStderr)
}
default:
t.Errorf("%s: unknown command", prefix)
}
}
if lastExitCode == noExitCode {
t.Errorf("test script contains no 'checker' command")
}
})
}
}
const badMarker = "[bad marker]"
// The marker analyzer generates fixes from @marker annotations in the
// source. Each marker is of the form:
//
// @message("pattern", "replacement)
//
// The "message" is used for both the Diagnostic.Message and
// SuggestedFix.Message field. Multiple markers with the same
// message form a single diagnostic and fix with a list of textedits.
//
// The "pattern" is a regular expression that must match on the
// current line (though it may extend beyond if the pattern starts
// with "(?s)"), and whose extent forms the TextEdit.{Pos,End}
// deletion. If the pattern contains one subgroup, its range will be
// used; this allows contextual matching.
//
// The "replacement" is a literal string that forms the
// TextEdit.NewText.
//
// Fixes are applied in the order they are first mentioned in the
// source.
var markerAnalyzer = &analysis.Analyzer{
Name: "marker",
Doc: "doc",
Requires: []*analysis.Analyzer{inspect.Analyzer},
Run: func(pass *analysis.Pass) (_ any, err error) {
// Errors returned by this analyzer cause the
// checker command to exit non-zero, but that
// may be the expected outcome for other reasons
// (e.g. there were diagnostics).
//
// So, we report these errors out of band by logging
// them with a special badMarker string that the
// TestScript harness looks for, to ensure that the
// test fails in that case.
defer func() {
if err != nil {
log.Printf("%s: %v", badMarker, err)
}
}()
// Parse all notes in the files.
var keys []string
edits := make(map[string][]analysis.TextEdit)
for _, file := range pass.Files {
tokFile := pass.Fset.File(file.FileStart)
content, err := pass.ReadFile(tokFile.Name())
if err != nil {
return nil, err
}
notes, err := expect.ExtractGo(tokFile, file)
if err != nil {
return nil, err
}
for _, note := range notes {
edit, err := markerEdit(tokFile, content, note)
if err != nil {
return nil, fmt.Errorf("%s: %v", tokFile.Position(note.Pos), err)
}
// Preserve note order as it determines fix order.
if edits[note.Name] == nil {
keys = append(keys, note.Name)
}
edits[note.Name] = append(edits[note.Name], edit)
}
}
// Report each fix in its own Diagnostic.
for _, key := range keys {
edits := edits[key]
// debugging
if false {
log.Printf("%s: marker: @%s: %+v", pass.Fset.Position(edits[0].Pos), key, edits)
}
pass.Report(analysis.Diagnostic{
Pos: edits[0].Pos,
End: edits[0].Pos,
Message: key,
SuggestedFixes: []analysis.SuggestedFix{{
Message: key,
TextEdits: edits,
}},
})
}
return nil, nil
},
}
// markerEdit returns the TextEdit denoted by note.
func markerEdit(tokFile *token.File, content []byte, note *expect.Note) (analysis.TextEdit, error) {
if len(note.Args) != 2 {
return analysis.TextEdit{}, fmt.Errorf("got %d args, want @%s(pattern, replacement)", len(note.Args), note.Name)
}
pattern, ok := note.Args[0].(string)
if !ok {
return analysis.TextEdit{}, fmt.Errorf("got %T for pattern, want string", note.Args[0])
}
rx, err := regexp.Compile(pattern)
if err != nil {
return analysis.TextEdit{}, fmt.Errorf("invalid pattern regexp: %v", err)
}
// Match the pattern against the current line.
lineStart := tokFile.LineStart(tokFile.Position(note.Pos).Line)
lineStartOff := tokFile.Offset(lineStart)
lineEndOff := tokFile.Offset(note.Pos)
matches := rx.FindSubmatchIndex(content[lineStartOff:])
if len(matches) == 0 {
return analysis.TextEdit{}, fmt.Errorf("no match for regexp %q", rx)
}
var start, end int // line-relative offset
switch len(matches) {
case 2:
// no subgroups: return the range of the regexp expression
start, end = matches[0], matches[1]
case 4:
// one subgroup: return its range
start, end = matches[2], matches[3]
default:
return analysis.TextEdit{}, fmt.Errorf("invalid location regexp %q: expect either 0 or 1 subgroups, got %d", rx, len(matches)/2-1)
}
if start > lineEndOff-lineStartOff {
// The start of the match must be between the start of the line and the
// marker position (inclusive).
return analysis.TextEdit{}, fmt.Errorf("no matching range found starting on the current line")
}
replacement, ok := note.Args[1].(string)
if !ok {
return analysis.TextEdit{}, fmt.Errorf("second argument must be pattern, got %T", note.Args[1])
}
// debugging: show matched portion
if false {
log.Printf("%s: %s: r%q (%q) -> %q",
tokFile.Position(note.Pos),
note.Name,
pattern,
content[lineStartOff+start:lineStartOff+end],
replacement)
}
return analysis.TextEdit{
Pos: lineStart + token.Pos(start),
End: lineStart + token.Pos(end),
NewText: []byte(replacement),
}, nil
}
var renameAnalyzer = &analysis.Analyzer{
Name: "rename",
Requires: []*analysis.Analyzer{inspect.Analyzer},
Doc: "renames symbols named bar to baz",
RunDespiteErrors: true,
Run: func(pass *analysis.Pass) (any, error) {
const (
from = "bar"
to = "baz"
)
inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
nodeFilter := []ast.Node{(*ast.Ident)(nil)}
inspect.Preorder(nodeFilter, func(n ast.Node) {
ident := n.(*ast.Ident)
if ident.Name == from {
msg := fmt.Sprintf("renaming %q to %q", from, to)
pass.Report(analysis.Diagnostic{
Pos: ident.Pos(),
End: ident.End(),
Message: msg,
SuggestedFixes: []analysis.SuggestedFix{{
Message: msg,
TextEdits: []analysis.TextEdit{{
Pos: ident.Pos(),
End: ident.End(),
NewText: []byte(to),
}},
}},
})
}
})
return nil, nil
},
}
var noendAnalyzer = &analysis.Analyzer{
Name: "noend",
Doc: "inserts /*hello*/ before first decl",
Run: func(pass *analysis.Pass) (any, error) {
decl := pass.Files[0].Decls[0]
pass.Report(analysis.Diagnostic{
Pos: decl.Pos(),
End: token.NoPos,
Message: "say hello",
SuggestedFixes: []analysis.SuggestedFix{{
Message: "say hello",
TextEdits: []analysis.TextEdit{{
Pos: decl.Pos(),
End: token.NoPos,
NewText: []byte("/*hello*/"),
}},
}},
})
return nil, nil
},
}
var relatedAnalyzer = &analysis.Analyzer{
Name: "related",
Doc: "reports a Diagnostic with RelatedInformaiton",
Run: func(pass *analysis.Pass) (any, error) {
decl := pass.Files[0].Decls[0]
pass.Report(analysis.Diagnostic{
Pos: decl.Pos(),
End: decl.Pos() + 1,
Message: "decl starts here",
Related: []analysis.RelatedInformation{{
Message: "decl ends here",
Pos: decl.End() - 1,
End: decl.End(),
}},
})
return nil, nil
},
}
// panics asserts that f() panics with with a value whose printed form matches the regexp want.
func panics(t *testing.T, want string, f func()) {
defer func() {
if x := recover(); x == nil {
t.Errorf("function returned normally, wanted panic")
} else if m, err := regexp.MatchString(want, fmt.Sprint(x)); err != nil {
t.Errorf("panics: invalid regexp %q", want)
} else if !m {
t.Errorf("function panicked with value %q, want match for %q", x, want)
}
}()
f()
}
// section returns the named archive section, or nil.
func section(ar *txtar.Archive, name string) *txtar.File {
for i, f := range ar.Files {
if f.Name == name {
return &ar.Files[i]
}
}
return nil
}
================================================
FILE: go/analysis/internal/checker/start_test.go
================================================
// Copyright 2022 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package checker_test
import (
"go/ast"
"os"
"path/filepath"
"testing"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/analysistest"
"golang.org/x/tools/go/analysis/internal/analysisflags"
"golang.org/x/tools/go/analysis/internal/checker"
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/ast/inspector"
"golang.org/x/tools/internal/testenv"
)
// TestStartFixes make sure modifying the first character
// of the file takes effect.
func TestStartFixes(t *testing.T) {
testenv.NeedsGoPackages(t)
testenv.RedirectStderr(t) // associated checker.Run output with this test
files := map[string]string{
"comment/doc.go": `/* Package comment */
package comment
`}
want := `// Package comment
package comment
`
testdata, cleanup, err := analysistest.WriteFiles(files)
if err != nil {
t.Fatal(err)
}
path := filepath.Join(testdata, "src/comment/doc.go")
analysisflags.Fix = true
checker.Run([]string{"file=" + path}, []*analysis.Analyzer{commentAnalyzer})
analysisflags.Fix = false
contents, err := os.ReadFile(path)
if err != nil {
t.Fatal(err)
}
got := string(contents)
if got != want {
t.Errorf("contents of rewritten file\ngot: %s\nwant: %s", got, want)
}
defer cleanup()
}
var commentAnalyzer = &analysis.Analyzer{
Name: "comment",
Doc: "comment",
Requires: []*analysis.Analyzer{inspect.Analyzer},
Run: commentRun,
}
func commentRun(pass *analysis.Pass) (any, error) {
const (
from = "/* Package comment */"
to = "// Package comment"
)
inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
inspect.Preorder(nil, func(n ast.Node) {
if n, ok := n.(*ast.Comment); ok && n.Text == from {
pass.Report(analysis.Diagnostic{
Pos: n.Pos(),
End: n.End(),
SuggestedFixes: []analysis.SuggestedFix{{
TextEdits: []analysis.TextEdit{{
Pos: n.Pos(),
End: n.End(),
NewText: []byte(to),
}},
}},
})
}
})
return nil, nil
}
================================================
FILE: go/analysis/internal/checker/testdata/conflict.txt
================================================
# Conflicting edits are legal, so long as they appear in different fixes.
# The driver will apply them in some order, and discard those that conflict.
#
# fix1 appears first, so is applied first; it succeeds.
# fix2 and fix3 conflict with it and are rejected.
checker -marker -fix example.com/a
exit 1
stderr applied 1 of 3 fixes; 1 file updated...Re-run
-- go.mod --
module example.com
go 1.22
-- a/a.go --
package a
func f() {
bar := 12 //@ fix1("\tbar", "baz"), fix2("ar ", "baz"), fix3("bar", "lorem ipsum")
_ = bar //@ fix1(" bar", "baz")
}
-- want/a/a.go --
package a
func f() {
baz := 12 //@ fix1("\tbar", "baz"), fix2("ar ", "baz"), fix3("bar", "lorem ipsum")
_ = baz //@ fix1(" bar", "baz")
}
================================================
FILE: go/analysis/internal/checker/testdata/diff.txt
================================================
# Basic test of -diff: ensure that stdout contains a diff,
# and the file system is unchanged.
#
# (Most tests of fixes should use want/* not -diff + stdout
# to avoid dependency on the diff algorithm.)
#
# File slashes assume non-Windows.
skip GOOS=windows
checker -rename -fix -diff example.com/p
exit 0
-- go.mod --
module example.com
go 1.22
-- p/p.go --
package p
var bar int
-- want/p/p.go --
package p
var bar int
-- stdout --
--- /TMP/p/p.go (old)
+++ /TMP/p/p.go (new)
@@ -1,4 +1,3 @@
package p
-var bar int
-
+var baz int
================================================
FILE: go/analysis/internal/checker/testdata/fixes.txt
================================================
# Ensure that fixes are applied correctly, in
# particular when processing duplicate fixes for overlapping packages
# in the same directory ("p", "p [p.test]", "p_test [p.test]").
checker -rename -fix -v example.com/p
stderr applied 8 fixes, updated 3 files
exit 0
-- go.mod --
module example.com
go 1.22
-- p/p.go --
package p
func Foo() {
bar := 12
_ = bar
}
-- p/p_test.go --
package p
func InTestFile() {
bar := 13
_ = bar
}
-- p/p_x_test.go --
package p_test
func Foo() {
bar := 14
_ = bar
}
-- want/p/p.go --
package p
func Foo() {
baz := 12
_ = baz
}
-- want/p/p_test.go --
package p
func InTestFile() {
baz := 13
_ = baz
}
-- want/p/p_x_test.go --
package p_test
func Foo() {
baz := 14
_ = baz
}
================================================
FILE: go/analysis/internal/checker/testdata/generated.txt
================================================
# This test exercises skipping of fixes that edit generated
# files, and the summary logging thereof.
checker -rename -marker -fix -v example.com/a
stderr skipped 1 fix that would edit generated files
stderr applied 2 fixes, updated 1 file
exit 0
-- go.mod --
module example.com
go 1.22
-- a/a.go --
package a
var A int //@ fix("A", "A2")
-- a/gena.go --
// Code generated by hand. DO NOT EDIT.
package a
var B int //@ fix2("B", "B2")
-- want/a/a.go --
package a
var A2 int //@ fix("A", "A2")
-- want/a/gena.go --
// Code generated by hand. DO NOT EDIT.
package a
var B int //@ fix2("B", "B2")
================================================
FILE: go/analysis/internal/checker/testdata/importdup.txt
================================================
# Test that duplicate imports--and, more generally, duplicate
# identical insertions--are coalesced.
checker -marker -fix -v example.com/a
stderr applied 2 fixes, updated 1 file
exit 0
-- go.mod --
module example.com
go 1.22
-- a/a.go --
package a
import (
_ "errors"
//@ fix1("()//", `"foo"`), fix2("()//", `"foo"`)
)
func f() {} //@ fix1("()}", "n++"), fix2("()}", "n++")
-- want/a/a.go --
package a
import (
_ "errors"
"foo" //@ fix1("()//", `"foo"`), fix2("()//", `"foo"`)
)
func f() { n++ } //@ fix1("()}", "n++"), fix2("()}", "n++")
================================================
FILE: go/analysis/internal/checker/testdata/importdup2.txt
================================================
# Test of import de-duplication behavior.
#
# In packages a and b, there are three fixes,
# each adding one of two imports, but in different order.
#
# In package a, the fixes are [foo, foo, bar],
# and they are resolved as follows:
# - foo is applied -> [foo]
# - foo is coalesced -> [foo]
# - bar is applied -> [foo bar]
# The result is then formatted to [bar foo].
#
# In package b, the fixes are [foo, bar, foo]:
# - foo is applied -> [foo]
# - bar is applied -> [foo bar]
# - foo is coalesced -> [foo bar]
# The same result is again formatted to [bar foo].
#
# In more complex examples, the result
# may be more subtly order-dependent.
checker -marker -fix -v example.com/a example.com/b
stderr applied 6 fixes, updated 2 files
exit 0
-- go.mod --
module example.com
go 1.22
-- a/a.go --
package a
import (
//@ fix1("()//", "\"foo\"\n"), fix2("()//", "\"foo\"\n"), fix3("()//", "\"bar\"\n")
)
-- want/a/a.go --
package a
import (
"bar"
"foo"
// @ fix1("()//", "\"foo\"\n"), fix2("()//", "\"foo\"\n"), fix3("()//", "\"bar\"\n")
)
-- b/b.go --
package b
import (
//@ fix1("()//", "\"foo\"\n"), fix2("()//", "\"bar\"\n"), fix3("()//", "\"foo\"\n")
)
-- want/b/b.go --
package b
import (
"bar"
"foo"
// @ fix1("()//", "\"foo\"\n"), fix2("()//", "\"bar\"\n"), fix3("()//", "\"foo\"\n")
)
================================================
FILE: go/analysis/internal/checker/testdata/json.txt
================================================
# Test basic JSON output.
# File slashes assume non-Windows.
skip GOOS=windows
checker -rename -json example.com/p
exit 0
-- go.mod --
module example.com
go 1.22
-- p/p.go --
package p
func f(bar int) {}
-- stdout --
{
"example.com/p": {
"rename": [
{
"posn": "/TMP/p/p.go:3:8",
"end": "/TMP/p/p.go:3:11",
"message": "renaming \"bar\" to \"baz\"",
"suggested_fixes": [
{
"message": "renaming \"bar\" to \"baz\"",
"edits": [
{
"filename": "/TMP/p/p.go",
"start": 18,
"end": 21,
"new": "baz"
}
]
}
]
}
]
}
}
================================================
FILE: go/analysis/internal/checker/testdata/noend.txt
================================================
# Test that a missing SuggestedFix.End position is correctly
# interpreted as if equal to SuggestedFix.Pos (see issue #64199).
checker -noend -fix example.com/a
exit 0
-- go.mod --
module example.com
go 1.22
-- a/a.go --
package a
func f() {}
-- want/a/a.go --
package a
/*hello*/
func f() {}
================================================
FILE: go/analysis/internal/checker/testdata/overlap.txt
================================================
# This test exercises an edge case of merging.
#
# Two analyzers generate overlapping fixes for this package:
# - 'rename' changes "bar" to "baz"
# - 'marker' changes "ar" to "baz"
# Historically this used to cause a conflict, but as it happens,
# the new merge algorithm splits the rename fix, since it overlaps
# the marker fix, into two subedits:
# - a deletion of "b" and
# - an edit from "ar" to "baz".
# The deletion is of course nonoverlapping, and the edit,
# by happy chance, is identical to the marker fix, so the two
# are coalesced.
#
# (This is a pretty unlikely situation, but it corresponds
# to a historical test, TestOther, that used to check for
# a conflict, and it seemed wrong to delete it without explanation.)
#
# The fixes are silently and successfully applied.
checker -rename -marker -fix -v example.com/a
stderr applied 2 fixes, updated 1 file
exit 0
-- go.mod --
module example.com
go 1.22
-- a/a.go --
package a
func f(bar int) {} //@ fix("ar", "baz")
-- want/a/a.go --
package a
func f(baz int) {} //@ fix("ar", "baz")
================================================
FILE: go/analysis/internal/checker/testdata/plain.txt
================================================
# Test plain output.
#
# File slashes assume non-Windows.
skip GOOS=windows
checker -related example.com/p
stderr p/p.go:3:1: decl starts here
stderr p/p.go:4:1: decl ends here
checker -related -c=0 example.com/p
stderr p/p.go:3:1: decl starts here
stderr 3 func f\(bar int\) {
stderr p/p.go:4:1: decl ends here
stderr 4 }
exit 3
-- go.mod --
module example.com
go 1.22
-- p/p.go --
package p
func f(bar int) {
}
================================================
FILE: go/analysis/internal/internal.go
================================================
// Copyright 2023 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package internal
import "golang.org/x/tools/go/analysis"
// This function is set by the checker package to provide
// backdoor access to the private Pass field
// of the *checker.Action type, for use by analysistest.
//
// It may return nil, for example if the action was not
// executed because of a failed dependent.
var ActionPass func(action any) *analysis.Pass
================================================
FILE: go/analysis/internal/versiontest/version_test.go
================================================
// Copyright 2023 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build go1.21
// Check that GoVersion propagates through to checkers.
// Depends on Go 1.21 go/types.
package versiontest
import (
"os"
"os/exec"
"path/filepath"
"strings"
"testing"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/analysistest"
"golang.org/x/tools/go/analysis/multichecker"
"golang.org/x/tools/go/analysis/singlechecker"
"golang.org/x/tools/internal/testenv"
)
var analyzer = &analysis.Analyzer{
Name: "versiontest",
Doc: "off",
Run: func(pass *analysis.Pass) (any, error) {
pass.Reportf(pass.Files[0].Package, "goversion=%s", pass.Pkg.GoVersion())
return nil, nil
},
}
func init() {
if os.Getenv("VERSIONTEST_MULTICHECKER") == "1" {
multichecker.Main(analyzer)
os.Exit(0)
}
if os.Getenv("VERSIONTEST_SINGLECHECKER") == "1" {
singlechecker.Main(analyzer)
os.Exit(0)
}
}
func testDir(t *testing.T) (dir string) {
dir = t.TempDir()
if err := os.WriteFile(filepath.Join(dir, "go.mod"), []byte("go 1.20\nmodule m\n"), 0666); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(filepath.Join(dir, "x.go"), []byte("package main // want \"goversion=go1.20\"\n"), 0666); err != nil {
t.Fatal(err)
}
return dir
}
// There are many ways to run analyzers. Test all the ones here in x/tools.
func TestAnalysistest(t *testing.T) {
analysistest.Run(t, testDir(t), analyzer)
}
func TestMultichecker(t *testing.T) {
testenv.NeedsGoPackages(t)
exe, err := os.Executable()
if err != nil {
t.Fatal(err)
}
cmd := exec.Command(exe, ".")
cmd.Dir = testDir(t)
cmd.Env = append(os.Environ(), "VERSIONTEST_MULTICHECKER=1")
out, err := cmd.CombinedOutput()
if err == nil || !strings.Contains(string(out), "x.go:1:1: goversion=go1.20") {
t.Fatalf("multichecker: %v\n%s", err, out)
}
}
func TestSinglechecker(t *testing.T) {
testenv.NeedsGoPackages(t)
exe, err := os.Executable()
if err != nil {
t.Fatal(err)
}
cmd := exec.Command(exe, ".")
cmd.Dir = testDir(t)
cmd.Env = append(os.Environ(), "VERSIONTEST_SINGLECHECKER=1")
out, err := cmd.CombinedOutput()
if err == nil || !strings.Contains(string(out), "x.go:1:1: goversion=go1.20") {
t.Fatalf("multichecker: %v\n%s", err, out)
}
}
func TestVettool(t *testing.T) {
testenv.NeedsGoPackages(t)
exe, err := os.Executable()
if err != nil {
t.Fatal(err)
}
cmd := exec.Command("go", "vet", "-vettool="+exe, ".")
cmd.Dir = testDir(t)
cmd.Env = append(os.Environ(), "VERSIONTEST_MULTICHECKER=1")
out, err := cmd.CombinedOutput()
if err == nil || !strings.Contains(string(out), "x.go:1:1: goversion=go1.20") {
t.Fatalf("vettool: %v\n%s", err, out)
}
}
================================================
FILE: go/analysis/multichecker/multichecker.go
================================================
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package multichecker defines the main function for an analysis driver
// with several analyzers. This package makes it easy for anyone to build
// an analysis tool containing just the analyzers they need.
package multichecker
import (
"flag"
"fmt"
"log"
"os"
"path/filepath"
"strings"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/internal/analysisflags"
"golang.org/x/tools/go/analysis/internal/checker"
"golang.org/x/tools/go/analysis/unitchecker"
)
func Main(analyzers ...*analysis.Analyzer) {
progname := filepath.Base(os.Args[0])
log.SetFlags(0)
log.SetPrefix(progname + ": ") // e.g. "vet: "
if err := analysis.Validate(analyzers); err != nil {
log.Fatal(err)
}
checker.RegisterFlags()
analyzers = analysisflags.Parse(analyzers, true)
args := flag.Args()
if len(args) == 0 {
fmt.Fprintf(os.Stderr, `%[1]s is a tool for static analysis of Go programs.
Usage: %[1]s [-flag] [package]
Run '%[1]s help' for more detail,
or '%[1]s help name' for details and flags of a specific analyzer.
`, progname)
os.Exit(1)
}
if args[0] == "help" {
analysisflags.Help(progname, analyzers, args[1:])
os.Exit(0)
}
if len(args) == 1 && strings.HasSuffix(args[0], ".cfg") {
unitchecker.Run(args[0], analyzers)
panic("unreachable")
}
os.Exit(checker.Run(args, analyzers))
}
================================================
FILE: go/analysis/multichecker/multichecker_test.go
================================================
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build go1.12
package multichecker_test
import (
"fmt"
"os"
"os/exec"
"runtime"
"testing"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/multichecker"
"golang.org/x/tools/go/analysis/passes/findcall"
"golang.org/x/tools/internal/testenv"
)
func main() {
fail := &analysis.Analyzer{
Name: "fail",
Doc: "always fail on a package 'sort'",
Run: func(pass *analysis.Pass) (any, error) {
if pass.Pkg.Path() == "sort" {
return nil, fmt.Errorf("failed")
}
return nil, nil
},
}
multichecker.Main(findcall.Analyzer, fail)
}
// TestExitCode ensures that analysis failures are reported correctly.
// This test fork/execs the main function above.
func TestExitCode(t *testing.T) {
if runtime.GOOS != "linux" {
t.Skipf("skipping fork/exec test on this platform")
}
if os.Getenv("MULTICHECKER_CHILD") == "1" {
// child process
// replace [progname -test.run=TestExitCode -- ...]
// by [progname ...]
os.Args = os.Args[2:]
os.Args[0] = "vet"
main()
panic("unreachable")
}
testenv.NeedsTool(t, "go")
for _, test := range []struct {
args []string
want int
}{
{[]string{"nosuchdir/..."}, 1}, // matched no packages
{[]string{"nosuchpkg"}, 1}, // matched no packages
{[]string{"-unknownflag"}, 2}, // flag error
{[]string{"-findcall.name=panic", "io"}, 3}, // finds diagnostics
{[]string{"-findcall=0", "io"}, 0}, // no checkers
{[]string{"-findcall.name=nosuchfunc", "io"}, 0}, // no diagnostics
{[]string{"-findcall.name=panic", "sort", "io"}, 1}, // 'fail' failed on 'sort'
// -json: exits zero even in face of diagnostics or package errors.
{[]string{"-findcall.name=panic", "-json", "io"}, 0},
{[]string{"-findcall.name=panic", "-json", "io"}, 0},
{[]string{"-findcall.name=panic", "-json", "sort", "io"}, 0},
} {
args := []string{"-test.run=TestExitCode", "--"}
args = append(args, test.args...)
cmd := exec.Command(os.Args[0], args...)
cmd.Env = append(os.Environ(), "MULTICHECKER_CHILD=1")
out, err := cmd.CombinedOutput()
if len(out) > 0 {
t.Logf("%s: out=<<%s>>", test.args, out)
}
var exitcode int
if err, ok := err.(*exec.ExitError); ok {
exitcode = err.ExitCode() // requires go1.12
}
if exitcode != test.want {
t.Errorf("%s: exited %d, want %d", test.args, exitcode, test.want)
}
}
}
================================================
FILE: go/analysis/passes/README
================================================
This directory does not contain a Go package,
but acts as a container for various analyses
that implement the golang.org/x/tools/go/analysis
API and may be imported into an analysis tool.
By convention, each package foo provides the analysis,
and each command foo/cmd/foo provides a standalone driver.
================================================
FILE: go/analysis/passes/appends/appends.go
================================================
// Copyright 2023 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package appends defines an Analyzer that detects
// if there is only one variable in append.
package appends
import (
_ "embed"
"go/ast"
"go/types"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/ast/inspector"
"golang.org/x/tools/go/types/typeutil"
"golang.org/x/tools/internal/analysis/analyzerutil"
)
//go:embed doc.go
var doc string
var Analyzer = &analysis.Analyzer{
Name: "appends",
Doc: analyzerutil.MustExtractDoc(doc, "appends"),
URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/appends",
Requires: []*analysis.Analyzer{inspect.Analyzer},
Run: run,
}
func run(pass *analysis.Pass) (any, error) {
inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
nodeFilter := []ast.Node{
(*ast.CallExpr)(nil),
}
inspect.Preorder(nodeFilter, func(n ast.Node) {
call := n.(*ast.CallExpr)
b, ok := typeutil.Callee(pass.TypesInfo, call).(*types.Builtin)
if ok && b.Name() == "append" && len(call.Args) == 1 {
pass.ReportRangef(call, "append with no values")
}
})
return nil, nil
}
================================================
FILE: go/analysis/passes/appends/appends_test.go
================================================
// Copyright 2023 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package appends_test
import (
"testing"
"golang.org/x/tools/go/analysis/analysistest"
"golang.org/x/tools/go/analysis/passes/appends"
)
func Test(t *testing.T) {
testdata := analysistest.TestData()
tests := []string{"a", "b"}
analysistest.Run(t, testdata, appends.Analyzer, tests...)
}
================================================
FILE: go/analysis/passes/appends/doc.go
================================================
// Copyright 2023 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package appends defines an Analyzer that detects
// if there is only one variable in append.
//
// # Analyzer appends
//
// appends: check for missing values after append
//
// This checker reports calls to append that pass
// no values to be appended to the slice.
//
// s := []string{"a", "b", "c"}
// _ = append(s)
//
// Such calls are always no-ops and often indicate an
// underlying mistake.
package appends
================================================
FILE: go/analysis/passes/appends/testdata/src/a/a.go
================================================
// Copyright 2023 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// This file contains tests for the appends checker.
package a
func badAppendSlice1() {
sli := []string{"a", "b", "c"}
sli = append(sli) // want "append with no values"
}
func badAppendSlice2() {
_ = append([]string{"a"}) // want "append with no values"
}
func goodAppendSlice1() {
sli := []string{"a", "b", "c"}
sli = append(sli, "d")
}
func goodAppendSlice2() {
sli1 := []string{"a", "b", "c"}
sli2 := []string{"d", "e", "f"}
sli1 = append(sli1, sli2...)
}
func goodAppendSlice3() {
sli := []string{"a", "b", "c"}
sli = append(sli, "d", "e", "f")
}
================================================
FILE: go/analysis/passes/appends/testdata/src/b/b.go
================================================
// Copyright 2023 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// This file contains tests for the appends checker.
package b
func append(args ...interface{}) []int {
println(args)
return []int{0}
}
func userdefine() {
sli := []int{1, 2, 3}
sli = append(sli, 4, 5, 6)
sli = append(sli)
}
func localvar() {
append := func(int) int { return 0 }
a := append(1)
_ = a
}
================================================
FILE: go/analysis/passes/asmdecl/asmdecl.go
================================================
// Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package asmdecl defines an Analyzer that reports mismatches between
// assembly files and Go declarations.
package asmdecl
import (
"bytes"
"fmt"
"go/ast"
"go/build"
"go/token"
"go/types"
"log"
"regexp"
"strconv"
"strings"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/internal/analysis/analyzerutil"
)
const Doc = "report mismatches between assembly files and Go declarations"
var Analyzer = &analysis.Analyzer{
Name: "asmdecl",
Doc: Doc,
URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/asmdecl",
Run: run,
}
// 'kind' is a kind of assembly variable.
// The kinds 1, 2, 4, 8 stand for values of that size.
type asmKind int
// These special kinds are not valid sizes.
const (
asmString asmKind = 100 + iota
asmSlice
asmArray
asmInterface
asmEmptyInterface
asmStruct
asmComplex
)
// An asmArch describes assembly parameters for an architecture
type asmArch struct {
name string
bigEndian bool
stack string
lr bool
// retRegs is a list of registers for return value in register ABI (ABIInternal).
// For now, as we only check whether we write to any result, here we only need to
// include the first integer register and first floating-point register. Accessing
// any of them counts as writing to result.
retRegs []string
// writeResult is a list of instructions that will change result register implicitly.
writeResult []string
// calculated during initialization
sizes types.Sizes
intSize int
ptrSize int
maxAlign int
}
// An asmFunc describes the expected variables for a function on a given architecture.
type asmFunc struct {
arch *asmArch
size int // size of all arguments
vars map[string]*asmVar
varByOffset map[int]*asmVar
}
// An asmVar describes a single assembly variable.
type asmVar struct {
name string
kind asmKind
typ string
off int
size int
inner []*asmVar
}
var (
asmArch386 = asmArch{name: "386", bigEndian: false, stack: "SP", lr: false}
asmArchArm = asmArch{name: "arm", bigEndian: false, stack: "R13", lr: true}
asmArchArm64 = asmArch{name: "arm64", bigEndian: false, stack: "RSP", lr: true, retRegs: []string{"R0", "F0"}, writeResult: []string{"SVC"}}
asmArchAmd64 = asmArch{name: "amd64", bigEndian: false, stack: "SP", lr: false, retRegs: []string{"AX", "X0"}, writeResult: []string{"SYSCALL"}}
asmArchMips = asmArch{name: "mips", bigEndian: true, stack: "R29", lr: true}
asmArchMipsLE = asmArch{name: "mipsle", bigEndian: false, stack: "R29", lr: true}
asmArchMips64 = asmArch{name: "mips64", bigEndian: true, stack: "R29", lr: true}
asmArchMips64LE = asmArch{name: "mips64le", bigEndian: false, stack: "R29", lr: true}
asmArchPpc64 = asmArch{name: "ppc64", bigEndian: true, stack: "R1", lr: true, retRegs: []string{"R3", "F1"}, writeResult: []string{"SYSCALL"}}
asmArchPpc64LE = asmArch{name: "ppc64le", bigEndian: false, stack: "R1", lr: true, retRegs: []string{"R3", "F1"}, writeResult: []string{"SYSCALL"}}
asmArchRISCV64 = asmArch{name: "riscv64", bigEndian: false, stack: "SP", lr: true, retRegs: []string{"X10", "F10"}, writeResult: []string{"ECALL"}}
asmArchS390X = asmArch{name: "s390x", bigEndian: true, stack: "R15", lr: true}
asmArchWasm = asmArch{name: "wasm", bigEndian: false, stack: "SP", lr: false}
asmArchLoong64 = asmArch{name: "loong64", bigEndian: false, stack: "R3", lr: true, retRegs: []string{"R4", "F0"}, writeResult: []string{"SYSCALL"}}
arches = []*asmArch{
&asmArch386,
&asmArchArm,
&asmArchArm64,
&asmArchAmd64,
&asmArchMips,
&asmArchMipsLE,
&asmArchMips64,
&asmArchMips64LE,
&asmArchPpc64,
&asmArchPpc64LE,
&asmArchRISCV64,
&asmArchS390X,
&asmArchWasm,
&asmArchLoong64,
}
)
func init() {
for _, arch := range arches {
arch.sizes = types.SizesFor("gc", arch.name)
if arch.sizes == nil {
// TODO(adonovan): fix: now that asmdecl is not in the standard
// library we cannot assume types.SizesFor is consistent with arches.
// For now, assume 64-bit norms and print a warning.
// But this warning should really be deferred until we attempt to use
// arch, which is very unlikely. Better would be
// to defer size computation until we have Pass.TypesSizes.
arch.sizes = types.SizesFor("gc", "amd64")
log.Printf("unknown architecture %s", arch.name)
}
arch.intSize = int(arch.sizes.Sizeof(types.Typ[types.Int]))
arch.ptrSize = int(arch.sizes.Sizeof(types.Typ[types.UnsafePointer]))
arch.maxAlign = int(arch.sizes.Alignof(types.Typ[types.Int64]))
}
}
var (
re = regexp.MustCompile
asmPlusBuild = re(`//\s+\+build\s+([^\n]+)`)
asmTEXT = re(`\bTEXT\b(.*)·([^\(]+)\(SB\)(?:\s*,\s*([0-9A-Z|+()]+))?(?:\s*,\s*\$(-?[0-9]+)(?:-([0-9]+))?)?`)
asmDATA = re(`\b(DATA|GLOBL)\b`)
asmNamedFP = re(`\$?([a-zA-Z0-9_\xFF-\x{10FFFF}]+)(?:\+([0-9]+))\(FP\)`)
asmUnnamedFP = re(`[^+\-0-9](([0-9]+)\(FP\))`)
asmSP = re(`[^+\-0-9](([0-9]+)\(([A-Z0-9]+)\))`)
asmOpcode = re(`^\s*(?:[A-Z0-9a-z_]+:)?\s*([A-Z]+)\s*([^,]*)(?:,\s*(.*))?`)
ppc64Suff = re(`([BHWD])(ZU|Z|U|BR)?$`)
abiSuff = re(`^(.+)<(ABI.+)>$`)
)
func run(pass *analysis.Pass) (any, error) {
// No work if no assembly files.
var sfiles []string
for _, fname := range pass.OtherFiles {
if strings.HasSuffix(fname, ".s") {
sfiles = append(sfiles, fname)
}
}
if sfiles == nil {
return nil, nil
}
// Gather declarations. knownFunc[name][arch] is func description.
knownFunc := make(map[string]map[string]*asmFunc)
for _, f := range pass.Files {
for _, decl := range f.Decls {
if decl, ok := decl.(*ast.FuncDecl); ok && decl.Body == nil {
knownFunc[decl.Name.Name] = asmParseDecl(pass, decl)
}
}
}
Files:
for _, fname := range sfiles {
content, tf, err := analyzerutil.ReadFile(pass, fname)
if err != nil {
return nil, err
}
// Determine architecture from file name if possible.
var arch string
var archDef *asmArch
for _, a := range arches {
if strings.HasSuffix(fname, "_"+a.name+".s") {
arch = a.name
archDef = a
break
}
}
lines := strings.SplitAfter(string(content), "\n")
var (
fn *asmFunc
fnName string
abi string
localSize, argSize int
wroteSP bool
noframe bool
haveRetArg bool
retLine []int
)
flushRet := func() {
if fn != nil && fn.vars["ret"] != nil && !haveRetArg && len(retLine) > 0 {
v := fn.vars["ret"]
resultStr := fmt.Sprintf("%d-byte ret+%d(FP)", v.size, v.off)
if abi == "ABIInternal" {
resultStr = "result register"
}
for _, line := range retLine {
pass.Reportf(tf.LineStart(line), "[%s] %s: RET without writing to %s", arch, fnName, resultStr)
}
}
retLine = nil
}
trimABI := func(fnName string) (string, string) {
m := abiSuff.FindStringSubmatch(fnName)
if m != nil {
return m[1], m[2]
}
return fnName, ""
}
for lineno, line := range lines {
lineno++
badf := func(format string, args ...any) {
pass.Reportf(tf.LineStart(lineno), "[%s] %s: %s", arch, fnName, fmt.Sprintf(format, args...))
}
if arch == "" {
// Determine architecture from +build line if possible.
if m := asmPlusBuild.FindStringSubmatch(line); m != nil {
// There can be multiple architectures in a single +build line,
// so accumulate them all and then prefer the one that
// matches build.Default.GOARCH.
var archCandidates []*asmArch
for fld := range strings.FieldsSeq(m[1]) {
for _, a := range arches {
if a.name == fld {
archCandidates = append(archCandidates, a)
}
}
}
for _, a := range archCandidates {
if a.name == build.Default.GOARCH {
archCandidates = []*asmArch{a}
break
}
}
if len(archCandidates) > 0 {
arch = archCandidates[0].name
archDef = archCandidates[0]
}
}
}
// Ignore comments and commented-out code.
if i := strings.Index(line, "//"); i >= 0 {
line = line[:i]
}
if m := asmTEXT.FindStringSubmatch(line); m != nil {
flushRet()
if arch == "" {
// Arch not specified by filename or build tags.
// Fall back to build.Default.GOARCH.
for _, a := range arches {
if a.name == build.Default.GOARCH {
arch = a.name
archDef = a
break
}
}
if arch == "" {
log.Printf("%s: cannot determine architecture for assembly file", fname)
continue Files
}
}
fnName = m[2]
if pkgPath := strings.TrimSpace(m[1]); pkgPath != "" {
// The assembler uses Unicode division slash within
// identifiers to represent the directory separator.
pkgPath = strings.Replace(pkgPath, "∕", "/", -1)
if pkgPath != pass.Pkg.Path() {
// log.Printf("%s:%d: [%s] cannot check cross-package assembly function: %s is in package %s", fname, lineno, arch, fnName, pkgPath)
fn = nil
fnName = ""
abi = ""
continue
}
}
// Trim off optional ABI selector.
fnName, abi = trimABI(fnName)
flag := m[3]
fn = knownFunc[fnName][arch]
if fn != nil {
size, _ := strconv.Atoi(m[5])
if size != fn.size && (flag != "7" && !strings.Contains(flag, "NOSPLIT") || size != 0) {
badf("wrong argument size %d; expected $...-%d", size, fn.size)
}
}
localSize, _ = strconv.Atoi(m[4])
localSize += archDef.intSize
if archDef.lr && !strings.Contains(flag, "NOFRAME") {
// Account for caller's saved LR
localSize += archDef.intSize
}
argSize, _ = strconv.Atoi(m[5])
noframe = strings.Contains(flag, "NOFRAME")
if fn == nil && !strings.Contains(fnName, "<>") && !noframe {
badf("function %s missing Go declaration", fnName)
}
wroteSP = false
haveRetArg = false
continue
} else if strings.Contains(line, "TEXT") && strings.Contains(line, "SB") {
// function, but not visible from Go (didn't match asmTEXT), so stop checking
flushRet()
fn = nil
fnName = ""
abi = ""
continue
}
if strings.Contains(line, "RET") && !strings.Contains(line, "(SB)") {
// RET f(SB) is a tail call. It is okay to not write the results.
retLine = append(retLine, lineno)
}
if fnName == "" {
continue
}
if asmDATA.FindStringSubmatch(line) != nil {
fn = nil
}
if archDef == nil {
continue
}
if strings.Contains(line, ", "+archDef.stack) || strings.Contains(line, ",\t"+archDef.stack) || strings.Contains(line, "NOP "+archDef.stack) || strings.Contains(line, "NOP\t"+archDef.stack) {
wroteSP = true
continue
}
if arch == "wasm" && strings.Contains(line, "CallImport") {
// CallImport is a call out to magic that can write the result.
haveRetArg = true
}
if abi == "ABIInternal" && !haveRetArg {
for _, ins := range archDef.writeResult {
if strings.Contains(line, ins) {
haveRetArg = true
break
}
}
for _, reg := range archDef.retRegs {
if strings.Contains(line, reg) {
haveRetArg = true
break
}
}
}
for _, m := range asmSP.FindAllStringSubmatch(line, -1) {
if m[3] != archDef.stack || wroteSP || noframe {
continue
}
off := 0
if m[1] != "" {
off, _ = strconv.Atoi(m[2])
}
if off >= localSize {
if fn != nil {
v := fn.varByOffset[off-localSize]
if v != nil {
badf("%s should be %s+%d(FP)", m[1], v.name, off-localSize)
continue
}
}
if off >= localSize+argSize {
badf("use of %s points beyond argument frame", m[1])
continue
}
badf("use of %s to access argument frame", m[1])
}
}
if fn == nil {
continue
}
for _, m := range asmUnnamedFP.FindAllStringSubmatch(line, -1) {
off, _ := strconv.Atoi(m[2])
v := fn.varByOffset[off]
if v != nil {
badf("use of unnamed argument %s; offset %d is %s+%d(FP)", m[1], off, v.name, v.off)
} else {
badf("use of unnamed argument %s", m[1])
}
}
for _, m := range asmNamedFP.FindAllStringSubmatch(line, -1) {
name := m[1]
off := 0
if m[2] != "" {
off, _ = strconv.Atoi(m[2])
}
if name == "ret" || strings.HasPrefix(name, "ret_") {
haveRetArg = true
}
v := fn.vars[name]
if v == nil {
// Allow argframe+0(FP).
if name == "argframe" && off == 0 {
continue
}
v = fn.varByOffset[off]
if v != nil {
badf("unknown variable %s; offset %d is %s+%d(FP)", name, off, v.name, v.off)
} else {
badf("unknown variable %s", name)
}
continue
}
asmCheckVar(badf, fn, line, m[0], off, v, archDef)
}
}
flushRet()
}
return nil, nil
}
func asmKindForType(t types.Type, size int) asmKind {
switch t := t.Underlying().(type) {
case *types.Basic:
switch t.Kind() {
case types.String:
return asmString
case types.Complex64, types.Complex128:
return asmComplex
}
return asmKind(size)
case *types.Pointer, *types.Chan, *types.Map, *types.Signature:
return asmKind(size)
case *types.Struct:
return asmStruct
case *types.Interface:
if t.Empty() {
return asmEmptyInterface
}
return asmInterface
case *types.Array:
return asmArray
case *types.Slice:
return asmSlice
}
panic("unreachable")
}
// A component is an assembly-addressable component of a composite type,
// or a composite type itself.
type component struct {
size int
offset int
kind asmKind
typ string
suffix string // Such as _base for string base, _0_lo for lo half of first element of [1]uint64 on 32 bit machine.
outer string // The suffix for immediately containing composite type.
}
func newComponent(suffix string, kind asmKind, typ string, offset, size int, outer string) component {
return component{suffix: suffix, kind: kind, typ: typ, offset: offset, size: size, outer: outer}
}
// componentsOfType generates a list of components of type t.
// For example, given string, the components are the string itself, the base, and the length.
func componentsOfType(arch *asmArch, t types.Type) []component {
return appendComponentsRecursive(arch, t, nil, "", 0)
}
// appendComponentsRecursive implements componentsOfType.
// Recursion is required to correct handle structs and arrays,
// which can contain arbitrary other types.
func appendComponentsRecursive(arch *asmArch, t types.Type, cc []component, suffix string, off int) []component {
s := t.String()
size := int(arch.sizes.Sizeof(t))
kind := asmKindForType(t, size)
cc = append(cc, newComponent(suffix, kind, s, off, size, suffix))
switch kind {
case 8:
if arch.ptrSize == 4 {
w1, w2 := "lo", "hi"
if arch.bigEndian {
w1, w2 = w2, w1
}
cc = append(cc, newComponent(suffix+"_"+w1, 4, "half "+s, off, 4, suffix))
cc = append(cc, newComponent(suffix+"_"+w2, 4, "half "+s, off+4, 4, suffix))
}
case asmEmptyInterface:
cc = append(cc, newComponent(suffix+"_type", asmKind(arch.ptrSize), "interface type", off, arch.ptrSize, suffix))
cc = append(cc, newComponent(suffix+"_data", asmKind(arch.ptrSize), "interface data", off+arch.ptrSize, arch.ptrSize, suffix))
case asmInterface:
cc = append(cc, newComponent(suffix+"_itable", asmKind(arch.ptrSize), "interface itable", off, arch.ptrSize, suffix))
cc = append(cc, newComponent(suffix+"_data", asmKind(arch.ptrSize), "interface data", off+arch.ptrSize, arch.ptrSize, suffix))
case asmSlice:
cc = append(cc, newComponent(suffix+"_base", asmKind(arch.ptrSize), "slice base", off, arch.ptrSize, suffix))
cc = append(cc, newComponent(suffix+"_len", asmKind(arch.intSize), "slice len", off+arch.ptrSize, arch.intSize, suffix))
cc = append(cc, newComponent(suffix+"_cap", asmKind(arch.intSize), "slice cap", off+arch.ptrSize+arch.intSize, arch.intSize, suffix))
case asmString:
cc = append(cc, newComponent(suffix+"_base", asmKind(arch.ptrSize), "string base", off, arch.ptrSize, suffix))
cc = append(cc, newComponent(suffix+"_len", asmKind(arch.intSize), "string len", off+arch.ptrSize, arch.intSize, suffix))
case asmComplex:
fsize := size / 2
cc = append(cc, newComponent(suffix+"_real", asmKind(fsize), fmt.Sprintf("real(complex%d)", size*8), off, fsize, suffix))
cc = append(cc, newComponent(suffix+"_imag", asmKind(fsize), fmt.Sprintf("imag(complex%d)", size*8), off+fsize, fsize, suffix))
case asmStruct:
tu := t.Underlying().(*types.Struct)
fields := make([]*types.Var, tu.NumFields())
for i := 0; i < tu.NumFields(); i++ {
fields[i] = tu.Field(i)
}
offsets := arch.sizes.Offsetsof(fields)
for i, f := range fields {
cc = appendComponentsRecursive(arch, f.Type(), cc, suffix+"_"+f.Name(), off+int(offsets[i]))
}
case asmArray:
tu := t.Underlying().(*types.Array)
elem := tu.Elem()
// Calculate offset of each element array.
fields := []*types.Var{
types.NewField(token.NoPos, nil, "fake0", elem, false),
types.NewField(token.NoPos, nil, "fake1", elem, false),
}
offsets := arch.sizes.Offsetsof(fields)
elemoff := int(offsets[1])
for i := 0; i < int(tu.Len()); i++ {
cc = appendComponentsRecursive(arch, elem, cc, suffix+"_"+strconv.Itoa(i), off+i*elemoff)
}
}
return cc
}
// asmParseDecl parses a function decl for expected assembly variables.
func asmParseDecl(pass *analysis.Pass, decl *ast.FuncDecl) map[string]*asmFunc {
var (
arch *asmArch
fn *asmFunc
offset int
)
// addParams adds asmVars for each of the parameters in list.
// isret indicates whether the list are the arguments or the return values.
// TODO(adonovan): simplify by passing (*types.Signature).{Params,Results}
// instead of list.
addParams := func(list []*ast.Field, isret bool) {
argnum := 0
for _, fld := range list {
t := pass.TypesInfo.Types[fld.Type].Type
// Work around https://golang.org/issue/28277.
if t == nil {
if ell, ok := fld.Type.(*ast.Ellipsis); ok {
t = types.NewSlice(pass.TypesInfo.Types[ell.Elt].Type)
}
}
align := int(arch.sizes.Alignof(t))
size := int(arch.sizes.Sizeof(t))
offset += -offset & (align - 1)
cc := componentsOfType(arch, t)
// names is the list of names with this type.
names := fld.Names
if len(names) == 0 {
// Anonymous args will be called arg, arg1, arg2, ...
// Similarly so for return values: ret, ret1, ret2, ...
name := "arg"
if isret {
name = "ret"
}
if argnum > 0 {
name += strconv.Itoa(argnum)
}
names = []*ast.Ident{ast.NewIdent(name)}
}
argnum += len(names)
// Create variable for each name.
for _, id := range names {
name := id.Name
for _, c := range cc {
outer := name + c.outer
v := asmVar{
name: name + c.suffix,
kind: c.kind,
typ: c.typ,
off: offset + c.offset,
size: c.size,
}
if vo := fn.vars[outer]; vo != nil {
vo.inner = append(vo.inner, &v)
}
fn.vars[v.name] = &v
for i := 0; i < v.size; i++ {
fn.varByOffset[v.off+i] = &v
}
}
offset += size
}
}
}
m := make(map[string]*asmFunc)
for _, arch = range arches {
fn = &asmFunc{
arch: arch,
vars: make(map[string]*asmVar),
varByOffset: make(map[int]*asmVar),
}
offset = 0
addParams(decl.Type.Params.List, false)
if decl.Type.Results != nil && len(decl.Type.Results.List) > 0 {
offset += -offset & (arch.maxAlign - 1)
addParams(decl.Type.Results.List, true)
}
fn.size = offset
m[arch.name] = fn
}
return m
}
// asmCheckVar checks a single variable reference.
func asmCheckVar(badf func(string, ...any), fn *asmFunc, line, expr string, off int, v *asmVar, archDef *asmArch) {
m := asmOpcode.FindStringSubmatch(line)
if m == nil {
if !strings.HasPrefix(strings.TrimSpace(line), "//") {
badf("cannot find assembly opcode")
}
return
}
addr := strings.HasPrefix(expr, "$")
// Determine operand sizes from instruction.
// Typically the suffix suffices, but there are exceptions.
var src, dst, kind asmKind
op := m[1]
switch fn.arch.name + "." + op {
case "386.FMOVLP":
src, dst = 8, 4
case "arm.MOVD":
src = 8
case "arm.MOVW":
src = 4
case "arm.MOVH", "arm.MOVHU":
src = 2
case "arm.MOVB", "arm.MOVBU":
src = 1
// LEA* opcodes don't really read the second arg.
// They just take the address of it.
case "386.LEAL":
dst = 4
addr = true
case "amd64.LEAQ":
dst = 8
addr = true
default:
switch fn.arch.name {
case "386", "amd64":
if strings.HasPrefix(op, "F") && (strings.HasSuffix(op, "D") || strings.HasSuffix(op, "DP")) {
// FMOVDP, FXCHD, etc
src = 8
break
}
if strings.HasPrefix(op, "P") && strings.HasSuffix(op, "RD") {
// PINSRD, PEXTRD, etc
src = 4
break
}
if strings.HasPrefix(op, "F") && (strings.HasSuffix(op, "F") || strings.HasSuffix(op, "FP")) {
// FMOVFP, FXCHF, etc
src = 4
break
}
if strings.HasSuffix(op, "SD") {
// MOVSD, SQRTSD, etc
src = 8
break
}
if strings.HasSuffix(op, "SS") {
// MOVSS, SQRTSS, etc
src = 4
break
}
if op == "MOVO" || op == "MOVOU" {
src = 16
break
}
if strings.HasPrefix(op, "SET") {
// SETEQ, etc
src = 1
break
}
switch op[len(op)-1] {
case 'B':
src = 1
case 'W':
src = 2
case 'L':
src = 4
case 'D', 'Q':
src = 8
}
case "ppc64", "ppc64le":
// Strip standard suffixes to reveal size letter.
m := ppc64Suff.FindStringSubmatch(op)
if m != nil {
switch m[1][0] {
case 'B':
src = 1
case 'H':
src = 2
case 'W':
src = 4
case 'D':
src = 8
}
}
case "loong64", "mips", "mipsle", "mips64", "mips64le":
switch op {
case "MOVB", "MOVBU":
src = 1
case "MOVH", "MOVHU":
src = 2
case "MOVW", "MOVWU", "MOVF":
src = 4
case "MOVV", "MOVD":
src = 8
}
case "s390x":
switch op {
case "MOVB", "MOVBZ":
src = 1
case "MOVH", "MOVHZ":
src = 2
case "MOVW", "MOVWZ", "FMOVS":
src = 4
case "MOVD", "FMOVD":
src = 8
}
}
}
if dst == 0 {
dst = src
}
// Determine whether the match we're holding
// is the first or second argument.
if strings.Index(line, expr) > strings.Index(line, ",") {
kind = dst
} else {
kind = src
}
vk := v.kind
vs := v.size
vt := v.typ
switch vk {
case asmInterface, asmEmptyInterface, asmString, asmSlice:
// allow reference to first word (pointer)
vk = v.inner[0].kind
vs = v.inner[0].size
vt = v.inner[0].typ
case asmComplex:
// Allow a single instruction to load both parts of a complex.
if int(kind) == vs {
kind = asmComplex
}
}
if addr {
vk = asmKind(archDef.ptrSize)
vs = archDef.ptrSize
vt = "address"
}
if off != v.off {
var inner bytes.Buffer
for i, vi := range v.inner {
if len(v.inner) > 1 {
fmt.Fprintf(&inner, ",")
}
fmt.Fprintf(&inner, " ")
if i == len(v.inner)-1 {
fmt.Fprintf(&inner, "or ")
}
fmt.Fprintf(&inner, "%s+%d(FP)", vi.name, vi.off)
}
badf("invalid offset %s; expected %s+%d(FP)%s", expr, v.name, v.off, inner.String())
return
}
if kind != 0 && kind != vk {
var inner bytes.Buffer
if len(v.inner) > 0 {
fmt.Fprintf(&inner, " containing")
for i, vi := range v.inner {
if i > 0 && len(v.inner) > 2 {
fmt.Fprintf(&inner, ",")
}
fmt.Fprintf(&inner, " ")
if i > 0 && i == len(v.inner)-1 {
fmt.Fprintf(&inner, "and ")
}
fmt.Fprintf(&inner, "%s+%d(FP)", vi.name, vi.off)
}
}
badf("invalid %s of %s; %s is %d-byte value%s", op, expr, vt, vs, inner.String())
}
}
================================================
FILE: go/analysis/passes/asmdecl/asmdecl_test.go
================================================
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package asmdecl_test
import (
"os"
"strings"
"testing"
"golang.org/x/tools/go/analysis/analysistest"
"golang.org/x/tools/go/analysis/passes/asmdecl"
)
var goosarches = []string{
"linux/amd64", // asm1.s, asm4.s
"linux/386", // asm2.s
"linux/arm", // asm3.s
// TODO: skip test on loong64 until go toolchain supported loong64.
// "linux/loong64", // asm10.s
"linux/mips64", // asm5.s
"linux/s390x", // asm6.s
"linux/ppc64", // asm7.s
"linux/mips", // asm8.s,
"js/wasm", // asm9.s
"linux/riscv64", // asm11.s
}
func Test(t *testing.T) {
testdata := analysistest.TestData()
for _, goosarch := range goosarches {
t.Run(goosarch, func(t *testing.T) {
i := strings.Index(goosarch, "/")
os.Setenv("GOOS", goosarch[:i])
os.Setenv("GOARCH", goosarch[i+1:])
analysistest.Run(t, testdata, asmdecl.Analyzer, "a")
})
}
}
================================================
FILE: go/analysis/passes/asmdecl/testdata/src/a/asm.go
================================================
// Copyright 2010 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// This file contains declarations to test the assembly in test_asm.s.
package a
type S struct {
i int32
b bool
s string
}
func arg1(x int8, y uint8)
func arg2(x int16, y uint16)
func arg4(x int32, y uint32)
func arg8(x int64, y uint64)
func argint(x int, y uint)
func argptr(x *byte, y *byte, c chan int, m map[int]int, f func())
func argstring(x, y string)
func argslice(x, y []string)
func argiface(x interface{}, y interface {
m()
})
func argcomplex(x complex64, y complex128)
func argstruct(x S, y struct{})
func argarray(x [2]S)
func returnint() int
func returnbyte(x int) byte
func returnnamed(x byte) (r1 int, r2 int16, r3 string, r4 byte)
func returnintmissing() int
func leaf(x, y int) int
func noprof(x int)
func dupok(x int)
func nosplit(x int)
func rodata(x int)
func noptr(x int)
func wrapper(x int)
func f15271() (x uint32)
func f17584(x float32, y complex64)
func f29318(x [2][2]uint64)
func noframe1(x int32)
func noframe2(x int32)
func fvariadic(int, ...int)
func pickStableABI(x int)
func pickInternalABI(x int)
func pickFutureABI(x int)
func returnABIInternal() int
func returnmissingABIInternal() int
func returnsyscallABIInternal() int
func retjmp() int
================================================
FILE: go/analysis/passes/asmdecl/testdata/src/a/asm1.s
================================================
// Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build amd64
// Commented-out code should be ignored.
//
// TEXT ·unknown(SB),0,$0
// RET
TEXT ·arg1(SB),0,$0-2
MOVB x+0(FP), AX
// MOVB x+0(FP), AX // commented out instructions used to panic
MOVB y+1(FP), BX
MOVW x+0(FP), AX // want `\[amd64\] arg1: invalid MOVW of x\+0\(FP\); int8 is 1-byte value`
MOVW y+1(FP), AX // want `invalid MOVW of y\+1\(FP\); uint8 is 1-byte value`
MOVL x+0(FP), AX // want `invalid MOVL of x\+0\(FP\); int8 is 1-byte value`
MOVL y+1(FP), AX // want `invalid MOVL of y\+1\(FP\); uint8 is 1-byte value`
MOVQ x+0(FP), AX // want `invalid MOVQ of x\+0\(FP\); int8 is 1-byte value`
MOVQ y+1(FP), AX // want `invalid MOVQ of y\+1\(FP\); uint8 is 1-byte value`
MOVB x+1(FP), AX // want `invalid offset x\+1\(FP\); expected x\+0\(FP\)`
MOVB y+2(FP), AX // want `invalid offset y\+2\(FP\); expected y\+1\(FP\)`
TESTB x+0(FP), AX
TESTB y+1(FP), BX
TESTW x+0(FP), AX // want `invalid TESTW of x\+0\(FP\); int8 is 1-byte value`
TESTW y+1(FP), AX // want `invalid TESTW of y\+1\(FP\); uint8 is 1-byte value`
TESTL x+0(FP), AX // want `invalid TESTL of x\+0\(FP\); int8 is 1-byte value`
TESTL y+1(FP), AX // want `invalid TESTL of y\+1\(FP\); uint8 is 1-byte value`
TESTQ x+0(FP), AX // want `invalid TESTQ of x\+0\(FP\); int8 is 1-byte value`
TESTQ y+1(FP), AX // want `invalid TESTQ of y\+1\(FP\); uint8 is 1-byte value`
TESTB x+1(FP), AX // want `invalid offset x\+1\(FP\); expected x\+0\(FP\)`
TESTB y+2(FP), AX // want `invalid offset y\+2\(FP\); expected y\+1\(FP\)`
MOVB 8(SP), AX // want `8\(SP\) should be x\+0\(FP\)`
MOVB 9(SP), AX // want `9\(SP\) should be y\+1\(FP\)`
MOVB 10(SP), AX // want `use of 10\(SP\) points beyond argument frame`
RET
TEXT ·arg2(SB),0,$0-4
MOVB x+0(FP), AX // want `arg2: invalid MOVB of x\+0\(FP\); int16 is 2-byte value`
MOVB y+2(FP), AX // want `invalid MOVB of y\+2\(FP\); uint16 is 2-byte value`
MOVW x+0(FP), AX
MOVW y+2(FP), BX
MOVL x+0(FP), AX // want `invalid MOVL of x\+0\(FP\); int16 is 2-byte value`
MOVL y+2(FP), AX // want `invalid MOVL of y\+2\(FP\); uint16 is 2-byte value`
MOVQ x+0(FP), AX // want `invalid MOVQ of x\+0\(FP\); int16 is 2-byte value`
MOVQ y+2(FP), AX // want `invalid MOVQ of y\+2\(FP\); uint16 is 2-byte value`
MOVW x+2(FP), AX // want `invalid offset x\+2\(FP\); expected x\+0\(FP\)`
MOVW y+0(FP), AX // want `invalid offset y\+0\(FP\); expected y\+2\(FP\)`
TESTB x+0(FP), AX // want `invalid TESTB of x\+0\(FP\); int16 is 2-byte value`
TESTB y+2(FP), AX // want `invalid TESTB of y\+2\(FP\); uint16 is 2-byte value`
TESTW x+0(FP), AX
TESTW y+2(FP), BX
TESTL x+0(FP), AX // want `invalid TESTL of x\+0\(FP\); int16 is 2-byte value`
TESTL y+2(FP), AX // want `invalid TESTL of y\+2\(FP\); uint16 is 2-byte value`
TESTQ x+0(FP), AX // want `invalid TESTQ of x\+0\(FP\); int16 is 2-byte value`
TESTQ y+2(FP), AX // want `invalid TESTQ of y\+2\(FP\); uint16 is 2-byte value`
TESTW x+2(FP), AX // want `invalid offset x\+2\(FP\); expected x\+0\(FP\)`
TESTW y+0(FP), AX // want `invalid offset y\+0\(FP\); expected y\+2\(FP\)`
RET
TEXT ·arg4(SB),0,$0-2 // want `arg4: wrong argument size 2; expected \$\.\.\.-8`
MOVB x+0(FP), AX // want `invalid MOVB of x\+0\(FP\); int32 is 4-byte value`
MOVB y+4(FP), BX // want `invalid MOVB of y\+4\(FP\); uint32 is 4-byte value`
MOVW x+0(FP), AX // want `invalid MOVW of x\+0\(FP\); int32 is 4-byte value`
MOVW y+4(FP), AX // want `invalid MOVW of y\+4\(FP\); uint32 is 4-byte value`
MOVL x+0(FP), AX
MOVL y+4(FP), AX
MOVQ x+0(FP), AX // want `invalid MOVQ of x\+0\(FP\); int32 is 4-byte value`
MOVQ y+4(FP), AX // want `invalid MOVQ of y\+4\(FP\); uint32 is 4-byte value`
MOVL x+4(FP), AX // want `invalid offset x\+4\(FP\); expected x\+0\(FP\)`
MOVL y+2(FP), AX // want `invalid offset y\+2\(FP\); expected y\+4\(FP\)`
TESTB x+0(FP), AX // want `invalid TESTB of x\+0\(FP\); int32 is 4-byte value`
TESTB y+4(FP), BX // want `invalid TESTB of y\+4\(FP\); uint32 is 4-byte value`
TESTW x+0(FP), AX // want `invalid TESTW of x\+0\(FP\); int32 is 4-byte value`
TESTW y+4(FP), AX // want `invalid TESTW of y\+4\(FP\); uint32 is 4-byte value`
TESTL x+0(FP), AX
TESTL y+4(FP), AX
TESTQ x+0(FP), AX // want `invalid TESTQ of x\+0\(FP\); int32 is 4-byte value`
TESTQ y+4(FP), AX // want `invalid TESTQ of y\+4\(FP\); uint32 is 4-byte value`
TESTL x+4(FP), AX // want `invalid offset x\+4\(FP\); expected x\+0\(FP\)`
TESTL y+2(FP), AX // want `invalid offset y\+2\(FP\); expected y\+4\(FP\)`
RET
TEXT ·arg8(SB),7,$0-2 // want `wrong argument size 2; expected \$\.\.\.-16`
MOVB x+0(FP), AX // want `invalid MOVB of x\+0\(FP\); int64 is 8-byte value`
MOVB y+8(FP), BX // want `invalid MOVB of y\+8\(FP\); uint64 is 8-byte value`
MOVW x+0(FP), AX // want `invalid MOVW of x\+0\(FP\); int64 is 8-byte value`
MOVW y+8(FP), AX // want `invalid MOVW of y\+8\(FP\); uint64 is 8-byte value`
MOVL x+0(FP), AX // want `invalid MOVL of x\+0\(FP\); int64 is 8-byte value`
MOVL y+8(FP), AX // want `invalid MOVL of y\+8\(FP\); uint64 is 8-byte value`
MOVQ x+0(FP), AX
MOVQ y+8(FP), AX
MOVQ x+8(FP), AX // want `invalid offset x\+8\(FP\); expected x\+0\(FP\)`
MOVQ y+2(FP), AX // want `invalid offset y\+2\(FP\); expected y\+8\(FP\)`
TESTB x+0(FP), AX // want `invalid TESTB of x\+0\(FP\); int64 is 8-byte value`
TESTB y+8(FP), BX // want `invalid TESTB of y\+8\(FP\); uint64 is 8-byte value`
TESTW x+0(FP), AX // want `invalid TESTW of x\+0\(FP\); int64 is 8-byte value`
TESTW y+8(FP), AX // want `invalid TESTW of y\+8\(FP\); uint64 is 8-byte value`
TESTL x+0(FP), AX // want `invalid TESTL of x\+0\(FP\); int64 is 8-byte value`
TESTL y+8(FP), AX // want `invalid TESTL of y\+8\(FP\); uint64 is 8-byte value`
TESTQ x+0(FP), AX
TESTQ y+8(FP), AX
TESTQ x+8(FP), AX // want `invalid offset x\+8\(FP\); expected x\+0\(FP\)`
TESTQ y+2(FP), AX // want `invalid offset y\+2\(FP\); expected y\+8\(FP\)`
RET
TEXT ·argint(SB),0,$0-2 // want `wrong argument size 2; expected \$\.\.\.-16`
MOVB x+0(FP), AX // want `invalid MOVB of x\+0\(FP\); int is 8-byte value`
MOVB y+8(FP), BX // want `invalid MOVB of y\+8\(FP\); uint is 8-byte value`
MOVW x+0(FP), AX // want `invalid MOVW of x\+0\(FP\); int is 8-byte value`
MOVW y+8(FP), AX // want `invalid MOVW of y\+8\(FP\); uint is 8-byte value`
MOVL x+0(FP), AX // want `invalid MOVL of x\+0\(FP\); int is 8-byte value`
MOVL y+8(FP), AX // want `invalid MOVL of y\+8\(FP\); uint is 8-byte value`
MOVQ x+0(FP), AX
MOVQ y+8(FP), AX
MOVQ x+8(FP), AX // want `invalid offset x\+8\(FP\); expected x\+0\(FP\)`
MOVQ y+2(FP), AX // want `invalid offset y\+2\(FP\); expected y\+8\(FP\)`
TESTB x+0(FP), AX // want `invalid TESTB of x\+0\(FP\); int is 8-byte value`
TESTB y+8(FP), BX // want `invalid TESTB of y\+8\(FP\); uint is 8-byte value`
TESTW x+0(FP), AX // want `invalid TESTW of x\+0\(FP\); int is 8-byte value`
TESTW y+8(FP), AX // want `invalid TESTW of y\+8\(FP\); uint is 8-byte value`
TESTL x+0(FP), AX // want `invalid TESTL of x\+0\(FP\); int is 8-byte value`
TESTL y+8(FP), AX // want `invalid TESTL of y\+8\(FP\); uint is 8-byte value`
TESTQ x+0(FP), AX
TESTQ y+8(FP), AX
TESTQ x+8(FP), AX // want `invalid offset x\+8\(FP\); expected x\+0\(FP\)`
TESTQ y+2(FP), AX // want `invalid offset y\+2\(FP\); expected y\+8\(FP\)`
RET
TEXT ·argptr(SB),7,$0-2 // want `wrong argument size 2; expected \$\.\.\.-40`
MOVB x+0(FP), AX // want `invalid MOVB of x\+0\(FP\); \*byte is 8-byte value`
MOVB y+8(FP), BX // want `invalid MOVB of y\+8\(FP\); \*byte is 8-byte value`
MOVW x+0(FP), AX // want `invalid MOVW of x\+0\(FP\); \*byte is 8-byte value`
MOVW y+8(FP), AX // want `invalid MOVW of y\+8\(FP\); \*byte is 8-byte value`
MOVL x+0(FP), AX // want `invalid MOVL of x\+0\(FP\); \*byte is 8-byte value`
MOVL y+8(FP), AX // want `invalid MOVL of y\+8\(FP\); \*byte is 8-byte value`
MOVQ x+0(FP), AX
MOVQ y+8(FP), AX
MOVQ x+8(FP), AX // want `invalid offset x\+8\(FP\); expected x\+0\(FP\)`
MOVQ y+2(FP), AX // want `invalid offset y\+2\(FP\); expected y\+8\(FP\)`
TESTB x+0(FP), AX // want `invalid TESTB of x\+0\(FP\); \*byte is 8-byte value`
TESTB y+8(FP), BX // want `invalid TESTB of y\+8\(FP\); \*byte is 8-byte value`
TESTW x+0(FP), AX // want `invalid TESTW of x\+0\(FP\); \*byte is 8-byte value`
TESTW y+8(FP), AX // want `invalid TESTW of y\+8\(FP\); \*byte is 8-byte value`
TESTL x+0(FP), AX // want `invalid TESTL of x\+0\(FP\); \*byte is 8-byte value`
TESTL y+8(FP), AX // want `invalid TESTL of y\+8\(FP\); \*byte is 8-byte value`
TESTQ x+0(FP), AX
TESTQ y+8(FP), AX
TESTQ x+8(FP), AX // want `invalid offset x\+8\(FP\); expected x\+0\(FP\)`
TESTQ y+2(FP), AX // want `invalid offset y\+2\(FP\); expected y\+8\(FP\)`
MOVL c+16(FP), AX // want `invalid MOVL of c\+16\(FP\); chan int is 8-byte value`
MOVL m+24(FP), AX // want `invalid MOVL of m\+24\(FP\); map\[int\]int is 8-byte value`
MOVL f+32(FP), AX // want `invalid MOVL of f\+32\(FP\); func\(\) is 8-byte value`
RET
TEXT ·argstring(SB),0,$32 // want `wrong argument size 0; expected \$\.\.\.-32`
MOVW x+0(FP), AX // want `invalid MOVW of x\+0\(FP\); string base is 8-byte value`
MOVL x+0(FP), AX // want `invalid MOVL of x\+0\(FP\); string base is 8-byte value`
LEAQ x+0(FP), AX // ok
MOVQ x+0(FP), AX
MOVW x_base+0(FP), AX // want `invalid MOVW of x_base\+0\(FP\); string base is 8-byte value`
MOVL x_base+0(FP), AX // want `invalid MOVL of x_base\+0\(FP\); string base is 8-byte value`
MOVQ x_base+0(FP), AX
MOVW x_len+0(FP), AX // want `invalid offset x_len\+0\(FP\); expected x_len\+8\(FP\)`
MOVL x_len+0(FP), AX // want `invalid offset x_len\+0\(FP\); expected x_len\+8\(FP\)`
MOVQ x_len+0(FP), AX // want `invalid offset x_len\+0\(FP\); expected x_len\+8\(FP\)`
MOVW x_len+8(FP), AX // want `invalid MOVW of x_len\+8\(FP\); string len is 8-byte value`
MOVL x_len+8(FP), AX // want `invalid MOVL of x_len\+8\(FP\); string len is 8-byte value`
MOVQ x_len+8(FP), AX
MOVQ y+0(FP), AX // want `invalid offset y\+0\(FP\); expected y\+16\(FP\)`
MOVQ y_len+8(FP), AX // want `invalid offset y_len\+8\(FP\); expected y_len\+24\(FP\)`
RET
TEXT ·argslice(SB),0,$48 // want `wrong argument size 0; expected \$\.\.\.-48`
MOVW x+0(FP), AX // want `invalid MOVW of x\+0\(FP\); slice base is 8-byte value`
MOVL x+0(FP), AX // want `invalid MOVL of x\+0\(FP\); slice base is 8-byte value`
MOVQ x+0(FP), AX
MOVW x_base+0(FP), AX // want `invalid MOVW of x_base\+0\(FP\); slice base is 8-byte value`
MOVL x_base+0(FP), AX // want `invalid MOVL of x_base\+0\(FP\); slice base is 8-byte value`
MOVQ x_base+0(FP), AX
MOVW x_len+0(FP), AX // want `invalid offset x_len\+0\(FP\); expected x_len\+8\(FP\)`
MOVL x_len+0(FP), AX // want `invalid offset x_len\+0\(FP\); expected x_len\+8\(FP\)`
MOVQ x_len+0(FP), AX // want `invalid offset x_len\+0\(FP\); expected x_len\+8\(FP\)`
MOVW x_len+8(FP), AX // want `invalid MOVW of x_len\+8\(FP\); slice len is 8-byte value`
MOVL x_len+8(FP), AX // want `invalid MOVL of x_len\+8\(FP\); slice len is 8-byte value`
MOVQ x_len+8(FP), AX
MOVW x_cap+0(FP), AX // want `invalid offset x_cap\+0\(FP\); expected x_cap\+16\(FP\)`
MOVL x_cap+0(FP), AX // want `invalid offset x_cap\+0\(FP\); expected x_cap\+16\(FP\)`
MOVQ x_cap+0(FP), AX // want `invalid offset x_cap\+0\(FP\); expected x_cap\+16\(FP\)`
MOVW x_cap+16(FP), AX // want `invalid MOVW of x_cap\+16\(FP\); slice cap is 8-byte value`
MOVL x_cap+16(FP), AX // want `invalid MOVL of x_cap\+16\(FP\); slice cap is 8-byte value`
MOVQ x_cap+16(FP), AX
MOVQ y+0(FP), AX // want `invalid offset y\+0\(FP\); expected y\+24\(FP\)`
MOVQ y_len+8(FP), AX // want `invalid offset y_len\+8\(FP\); expected y_len\+32\(FP\)`
MOVQ y_cap+16(FP), AX // want `invalid offset y_cap\+16\(FP\); expected y_cap\+40\(FP\)`
RET
TEXT ·argiface(SB),0,$0-32
MOVW x+0(FP), AX // want `invalid MOVW of x\+0\(FP\); interface type is 8-byte value`
MOVL x+0(FP), AX // want `invalid MOVL of x\+0\(FP\); interface type is 8-byte value`
MOVQ x+0(FP), AX
MOVW x_type+0(FP), AX // want `invalid MOVW of x_type\+0\(FP\); interface type is 8-byte value`
MOVL x_type+0(FP), AX // want `invalid MOVL of x_type\+0\(FP\); interface type is 8-byte value`
MOVQ x_type+0(FP), AX
MOVQ x_itable+0(FP), AX // want `unknown variable x_itable; offset 0 is x_type\+0\(FP\)`
MOVQ x_itable+1(FP), AX // want `unknown variable x_itable; offset 1 is x_type\+0\(FP\)`
MOVW x_data+0(FP), AX // want `invalid offset x_data\+0\(FP\); expected x_data\+8\(FP\)`
MOVL x_data+0(FP), AX // want `invalid offset x_data\+0\(FP\); expected x_data\+8\(FP\)`
MOVQ x_data+0(FP), AX // want `invalid offset x_data\+0\(FP\); expected x_data\+8\(FP\)`
MOVW x_data+8(FP), AX // want `invalid MOVW of x_data\+8\(FP\); interface data is 8-byte value`
MOVL x_data+8(FP), AX // want `invalid MOVL of x_data\+8\(FP\); interface data is 8-byte value`
MOVQ x_data+8(FP), AX
MOVW y+16(FP), AX // want `invalid MOVW of y\+16\(FP\); interface itable is 8-byte value`
MOVL y+16(FP), AX // want `invalid MOVL of y\+16\(FP\); interface itable is 8-byte value`
MOVQ y+16(FP), AX
MOVW y_itable+16(FP), AX // want `invalid MOVW of y_itable\+16\(FP\); interface itable is 8-byte value`
MOVL y_itable+16(FP), AX // want `invalid MOVL of y_itable\+16\(FP\); interface itable is 8-byte value`
MOVQ y_itable+16(FP), AX
MOVQ y_type+16(FP), AX // want `unknown variable y_type; offset 16 is y_itable\+16\(FP\)`
MOVW y_data+16(FP), AX // want `invalid offset y_data\+16\(FP\); expected y_data\+24\(FP\)`
MOVL y_data+16(FP), AX // want `invalid offset y_data\+16\(FP\); expected y_data\+24\(FP\)`
MOVQ y_data+16(FP), AX // want `invalid offset y_data\+16\(FP\); expected y_data\+24\(FP\)`
MOVW y_data+24(FP), AX // want `invalid MOVW of y_data\+24\(FP\); interface data is 8-byte value`
MOVL y_data+24(FP), AX // want `invalid MOVL of y_data\+24\(FP\); interface data is 8-byte value`
MOVQ y_data+24(FP), AX
RET
TEXT ·argcomplex(SB),0,$24 // want `wrong argument size 0; expected \$\.\.\.-24`
MOVSS x+0(FP), X0 // want `invalid MOVSS of x\+0\(FP\); complex64 is 8-byte value containing x_real\+0\(FP\) and x_imag\+4\(FP\)`
MOVSS x_real+0(FP), X0
MOVSD x_real+0(FP), X0 // want `invalid MOVSD of x_real\+0\(FP\); real\(complex64\) is 4-byte value`
MOVSS x_real+4(FP), X0 // want `invalid offset x_real\+4\(FP\); expected x_real\+0\(FP\)`
MOVSS x_imag+4(FP), X0
MOVSD x_imag+4(FP), X0 // want `invalid MOVSD of x_imag\+4\(FP\); imag\(complex64\) is 4-byte value`
MOVSS x_imag+8(FP), X0 // want `invalid offset x_imag\+8\(FP\); expected x_imag\+4\(FP\)`
MOVSD y+8(FP), X0 // want `invalid MOVSD of y\+8\(FP\); complex128 is 16-byte value containing y_real\+8\(FP\) and y_imag\+16\(FP\)`
MOVSS y_real+8(FP), X0 // want `invalid MOVSS of y_real\+8\(FP\); real\(complex128\) is 8-byte value`
MOVSD y_real+8(FP), X0
MOVSS y_real+16(FP), X0 // want `invalid offset y_real\+16\(FP\); expected y_real\+8\(FP\)`
MOVSS y_imag+16(FP), X0 // want `invalid MOVSS of y_imag\+16\(FP\); imag\(complex128\) is 8-byte value`
MOVSD y_imag+16(FP), X0
MOVSS y_imag+24(FP), X0 // want `invalid offset y_imag\+24\(FP\); expected y_imag\+16\(FP\)`
// Loading both parts of a complex is ok: see issue 35264.
MOVSD x+0(FP), X0
MOVO y+8(FP), X0
MOVOU y+8(FP), X0
// These are not ok.
MOVO x+0(FP), X0 // want `invalid MOVO of x\+0\(FP\); complex64 is 8-byte value containing x_real\+0\(FP\) and x_imag\+4\(FP\)`
MOVOU x+0(FP), X0 // want `invalid MOVOU of x\+0\(FP\); complex64 is 8-byte value containing x_real\+0\(FP\) and x_imag\+4\(FP\)`
RET
TEXT ·argstruct(SB),0,$64 // want `wrong argument size 0; expected \$\.\.\.-24`
MOVQ x+0(FP), AX // want `invalid MOVQ of x\+0\(FP\); a.S is 24-byte value`
MOVQ x_i+0(FP), AX // want `invalid MOVQ of x_i\+0\(FP\); int32 is 4-byte value`
MOVQ x_b+0(FP), AX // want `invalid offset x_b\+0\(FP\); expected x_b\+4\(FP\)`
MOVQ x_s+8(FP), AX
MOVQ x_s_base+8(FP), AX
MOVQ x_s+16(FP), AX // want `invalid offset x_s\+16\(FP\); expected x_s\+8\(FP\), x_s_base\+8\(FP\), or x_s_len\+16\(FP\)`
MOVQ x_s_len+16(FP), AX
RET
TEXT ·argarray(SB),0,$64 // want `wrong argument size 0; expected \$\.\.\.-48`
MOVQ x+0(FP), AX // want `invalid MOVQ of x\+0\(FP\); \[2\]a.S is 48-byte value`
MOVQ x_0_i+0(FP), AX // want `invalid MOVQ of x_0_i\+0\(FP\); int32 is 4-byte value`
MOVQ x_0_b+0(FP), AX // want `invalid offset x_0_b\+0\(FP\); expected x_0_b\+4\(FP\)`
MOVQ x_0_s+8(FP), AX
MOVQ x_0_s_base+8(FP), AX
MOVQ x_0_s+16(FP), AX // want `invalid offset x_0_s\+16\(FP\); expected x_0_s\+8\(FP\), x_0_s_base\+8\(FP\), or x_0_s_len\+16\(FP\)`
MOVQ x_0_s_len+16(FP), AX
MOVB foo+25(FP), AX // want `unknown variable foo; offset 25 is x_1_i\+24\(FP\)`
MOVQ x_1_s+32(FP), AX
MOVQ x_1_s_base+32(FP), AX
MOVQ x_1_s+40(FP), AX // want `invalid offset x_1_s\+40\(FP\); expected x_1_s\+32\(FP\), x_1_s_base\+32\(FP\), or x_1_s_len\+40\(FP\)`
MOVQ x_1_s_len+40(FP), AX
RET
TEXT ·returnint(SB),0,$0-8
MOVB AX, ret+0(FP) // want `invalid MOVB of ret\+0\(FP\); int is 8-byte value`
MOVW AX, ret+0(FP) // want `invalid MOVW of ret\+0\(FP\); int is 8-byte value`
MOVL AX, ret+0(FP) // want `invalid MOVL of ret\+0\(FP\); int is 8-byte value`
MOVQ AX, ret+0(FP)
MOVQ AX, ret+1(FP) // want `invalid offset ret\+1\(FP\); expected ret\+0\(FP\)`
MOVQ AX, r+0(FP) // want `unknown variable r; offset 0 is ret\+0\(FP\)`
RET
TEXT ·returnbyte(SB),0,$0-9
MOVQ x+0(FP), AX
MOVB AX, ret+8(FP)
MOVW AX, ret+8(FP) // want `invalid MOVW of ret\+8\(FP\); byte is 1-byte value`
MOVL AX, ret+8(FP) // want `invalid MOVL of ret\+8\(FP\); byte is 1-byte value`
MOVQ AX, ret+8(FP) // want `invalid MOVQ of ret\+8\(FP\); byte is 1-byte value`
MOVB AX, ret+7(FP) // want `invalid offset ret\+7\(FP\); expected ret\+8\(FP\)`
RET
TEXT ·returnnamed(SB),0,$0-41
MOVB x+0(FP), AX
MOVQ AX, r1+8(FP)
MOVW AX, r2+16(FP)
MOVQ AX, r3+24(FP)
MOVQ AX, r3_base+24(FP)
MOVQ AX, r3_len+32(FP)
MOVB AX, r4+40(FP)
MOVL AX, r1+8(FP) // want `invalid MOVL of r1\+8\(FP\); int is 8-byte value`
RET
TEXT ·returnintmissing(SB),0,$0-8
RET // want `RET without writing to 8-byte ret\+0\(FP\)`
// issue 15271
TEXT ·f15271(SB), NOSPLIT, $0-4
// Stick 123 into the low 32 bits of X0.
MOVQ $123, AX
PINSRD $0, AX, X0
// Return them.
PEXTRD $0, X0, x+0(FP)
RET
// issue 17584
TEXT ·f17584(SB), NOSPLIT, $12
MOVSS x+0(FP), X0
MOVSS y_real+4(FP), X0
MOVSS y_imag+8(FP), X0
RET
// issue 29318
TEXT ·f29318(SB), NOSPLIT, $32
MOVQ x_0_1+8(FP), AX
MOVQ x_1_1+24(FP), CX
RET
// ABI selector
TEXT ·pickStableABI(SB), NOSPLIT, $32
MOVQ x+0(FP), AX
RET
// ABI selector
TEXT ·pickInternalABI(SB), NOSPLIT, $32
MOVQ x+0(FP), AX
RET
// ABI selector
TEXT ·pickFutureABI(SB), NOSPLIT, $32
MOVQ x+0(FP), AX
RET
// writing to result in ABIInternal function
TEXT ·returnABIInternal(SB), NOSPLIT, $32
MOVQ $123, AX
RET
TEXT ·returnmissingABIInternal(SB), NOSPLIT, $32
MOVQ $123, CX
RET // want `RET without writing to result register`
// issue 69352
TEXT ·returnsyscallABIInternal(SB), NOSPLIT, $0
MOVQ $123, CX
SYSCALL
RET
// return jump
TEXT ·retjmp(SB), NOSPLIT, $0-8
RET retjmp1(SB) // It's okay to not write results if there's a tail call.
================================================
FILE: go/analysis/passes/asmdecl/testdata/src/a/asm10.s
================================================
// Copyright 2022 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build loong64
TEXT ·arg1(SB),0,$0-2
MOVB x+0(FP), R19
MOVBU y+1(FP), R18
MOVH x+0(FP), R19 // want `\[loong64\] arg1: invalid MOVH of x\+0\(FP\); int8 is 1-byte value`
MOVHU y+1(FP), R19 // want `invalid MOVHU of y\+1\(FP\); uint8 is 1-byte value`
MOVW x+0(FP), R19 // want `invalid MOVW of x\+0\(FP\); int8 is 1-byte value`
MOVWU y+1(FP), R19 // want `invalid MOVWU of y\+1\(FP\); uint8 is 1-byte value`
MOVV x+0(FP), R19 // want `invalid MOVV of x\+0\(FP\); int8 is 1-byte value`
MOVV y+1(FP), R19 // want `invalid MOVV of y\+1\(FP\); uint8 is 1-byte value`
MOVB x+1(FP), R19 // want `invalid offset x\+1\(FP\); expected x\+0\(FP\)`
MOVBU y+2(FP), R19 // want `invalid offset y\+2\(FP\); expected y\+1\(FP\)`
MOVB 16(R3), R19 // want `16\(R3\) should be x\+0\(FP\)`
MOVB 17(R3), R19 // want `17\(R3\) should be y\+1\(FP\)`
MOVB 18(R3), R19 // want `use of 18\(R3\) points beyond argument frame`
RET
TEXT ·arg2(SB),0,$0-4
MOVBU x+0(FP), R19 // want `arg2: invalid MOVBU of x\+0\(FP\); int16 is 2-byte value`
MOVB y+2(FP), R19 // want `invalid MOVB of y\+2\(FP\); uint16 is 2-byte value`
MOVHU x+0(FP), R19
MOVH y+2(FP), R18
MOVWU x+0(FP), R19 // want `invalid MOVWU of x\+0\(FP\); int16 is 2-byte value`
MOVW y+2(FP), R19 // want `invalid MOVW of y\+2\(FP\); uint16 is 2-byte value`
MOVV x+0(FP), R19 // want `invalid MOVV of x\+0\(FP\); int16 is 2-byte value`
MOVV y+2(FP), R19 // want `invalid MOVV of y\+2\(FP\); uint16 is 2-byte value`
MOVHU x+2(FP), R19 // want `invalid offset x\+2\(FP\); expected x\+0\(FP\)`
MOVH y+0(FP), R19 // want `invalid offset y\+0\(FP\); expected y\+2\(FP\)`
RET
TEXT ·arg4(SB),0,$0-2 // want `arg4: wrong argument size 2; expected \$\.\.\.-8`
MOVB x+0(FP), R19 // want `invalid MOVB of x\+0\(FP\); int32 is 4-byte value`
MOVB y+4(FP), R18 // want `invalid MOVB of y\+4\(FP\); uint32 is 4-byte value`
MOVH x+0(FP), R19 // want `invalid MOVH of x\+0\(FP\); int32 is 4-byte value`
MOVH y+4(FP), R19 // want `invalid MOVH of y\+4\(FP\); uint32 is 4-byte value`
MOVW x+0(FP), R19
MOVW y+4(FP), R19
MOVV x+0(FP), R19 // want `invalid MOVV of x\+0\(FP\); int32 is 4-byte value`
MOVV y+4(FP), R19 // want `invalid MOVV of y\+4\(FP\); uint32 is 4-byte value`
MOVW x+4(FP), R19 // want `invalid offset x\+4\(FP\); expected x\+0\(FP\)`
MOVW y+2(FP), R19 // want `invalid offset y\+2\(FP\); expected y\+4\(FP\)`
RET
TEXT ·arg8(SB),7,$0-2 // want `wrong argument size 2; expected \$\.\.\.-16`
MOVB x+0(FP), R19 // want `invalid MOVB of x\+0\(FP\); int64 is 8-byte value`
MOVB y+8(FP), R18 // want `invalid MOVB of y\+8\(FP\); uint64 is 8-byte value`
MOVH x+0(FP), R19 // want `invalid MOVH of x\+0\(FP\); int64 is 8-byte value`
MOVH y+8(FP), R19 // want `invalid MOVH of y\+8\(FP\); uint64 is 8-byte value`
MOVW x+0(FP), R19 // want `invalid MOVW of x\+0\(FP\); int64 is 8-byte value`
MOVW y+8(FP), R19 // want `invalid MOVW of y\+8\(FP\); uint64 is 8-byte value`
MOVV x+0(FP), R19
MOVV y+8(FP), R19
MOVV x+8(FP), R19 // want `invalid offset x\+8\(FP\); expected x\+0\(FP\)`
MOVV y+2(FP), R19 // want `invalid offset y\+2\(FP\); expected y\+8\(FP\)`
RET
TEXT ·argint(SB),0,$0-2 // want `wrong argument size 2; expected \$\.\.\.-16`
MOVB x+0(FP), R19 // want `invalid MOVB of x\+0\(FP\); int is 8-byte value`
MOVB y+8(FP), R18 // want `invalid MOVB of y\+8\(FP\); uint is 8-byte value`
MOVH x+0(FP), R19 // want `invalid MOVH of x\+0\(FP\); int is 8-byte value`
MOVH y+8(FP), R19 // want `invalid MOVH of y\+8\(FP\); uint is 8-byte value`
MOVW x+0(FP), R19 // want `invalid MOVW of x\+0\(FP\); int is 8-byte value`
MOVW y+8(FP), R19 // want `invalid MOVW of y\+8\(FP\); uint is 8-byte value`
MOVV x+0(FP), R19
MOVV y+8(FP), R19
MOVV x+8(FP), R19 // want `invalid offset x\+8\(FP\); expected x\+0\(FP\)`
MOVV y+2(FP), R19 // want `invalid offset y\+2\(FP\); expected y\+8\(FP\)`
RET
TEXT ·argptr(SB),7,$0-2 // want `wrong argument size 2; expected \$\.\.\.-40`
MOVB x+0(FP), R19 // want `invalid MOVB of x\+0\(FP\); \*byte is 8-byte value`
MOVB y+8(FP), R18 // want `invalid MOVB of y\+8\(FP\); \*byte is 8-byte value`
MOVH x+0(FP), R19 // want `invalid MOVH of x\+0\(FP\); \*byte is 8-byte value`
MOVH y+8(FP), R19 // want `invalid MOVH of y\+8\(FP\); \*byte is 8-byte value`
MOVW x+0(FP), R19 // want `invalid MOVW of x\+0\(FP\); \*byte is 8-byte value`
MOVW y+8(FP), R19 // want `invalid MOVW of y\+8\(FP\); \*byte is 8-byte value`
MOVV x+0(FP), R19
MOVV y+8(FP), R19
MOVV x+8(FP), R19 // want `invalid offset x\+8\(FP\); expected x\+0\(FP\)`
MOVV y+2(FP), R19 // want `invalid offset y\+2\(FP\); expected y\+8\(FP\)`
MOVW c+16(FP), R19 // want `invalid MOVW of c\+16\(FP\); chan int is 8-byte value`
MOVW m+24(FP), R19 // want `invalid MOVW of m\+24\(FP\); map\[int\]int is 8-byte value`
MOVW f+32(FP), R19 // want `invalid MOVW of f\+32\(FP\); func\(\) is 8-byte value`
RET
TEXT ·argstring(SB),0,$32 // want `wrong argument size 0; expected \$\.\.\.-32`
MOVH x+0(FP), R19 // want `invalid MOVH of x\+0\(FP\); string base is 8-byte value`
MOVW x+0(FP), R19 // want `invalid MOVW of x\+0\(FP\); string base is 8-byte value`
MOVV x+0(FP), R19
MOVH x_base+0(FP), R19 // want `invalid MOVH of x_base\+0\(FP\); string base is 8-byte value`
MOVW x_base+0(FP), R19 // want `invalid MOVW of x_base\+0\(FP\); string base is 8-byte value`
MOVV x_base+0(FP), R19
MOVH x_len+0(FP), R19 // want `invalid offset x_len\+0\(FP\); expected x_len\+8\(FP\)`
MOVW x_len+0(FP), R19 // want `invalid offset x_len\+0\(FP\); expected x_len\+8\(FP\)`
MOVV x_len+0(FP), R19 // want `invalid offset x_len\+0\(FP\); expected x_len\+8\(FP\)`
MOVH x_len+8(FP), R19 // want `invalid MOVH of x_len\+8\(FP\); string len is 8-byte value`
MOVW x_len+8(FP), R19 // want `invalid MOVW of x_len\+8\(FP\); string len is 8-byte value`
MOVV x_len+8(FP), R19
MOVV y+0(FP), R19 // want `invalid offset y\+0\(FP\); expected y\+16\(FP\)`
MOVV y_len+8(FP), R19 // want `invalid offset y_len\+8\(FP\); expected y_len\+24\(FP\)`
RET
TEXT ·argslice(SB),0,$48 // want `wrong argument size 0; expected \$\.\.\.-48`
MOVH x+0(FP), R19 // want `invalid MOVH of x\+0\(FP\); slice base is 8-byte value`
MOVW x+0(FP), R19 // want `invalid MOVW of x\+0\(FP\); slice base is 8-byte value`
MOVV x+0(FP), R19
MOVH x_base+0(FP), R19 // want `invalid MOVH of x_base\+0\(FP\); slice base is 8-byte value`
MOVW x_base+0(FP), R19 // want `invalid MOVW of x_base\+0\(FP\); slice base is 8-byte value`
MOVV x_base+0(FP), R19
MOVH x_len+0(FP), R19 // want `invalid offset x_len\+0\(FP\); expected x_len\+8\(FP\)`
MOVW x_len+0(FP), R19 // want `invalid offset x_len\+0\(FP\); expected x_len\+8\(FP\)`
MOVV x_len+0(FP), R19 // want `invalid offset x_len\+0\(FP\); expected x_len\+8\(FP\)`
MOVH x_len+8(FP), R19 // want `invalid MOVH of x_len\+8\(FP\); slice len is 8-byte value`
MOVW x_len+8(FP), R19 // want `invalid MOVW of x_len\+8\(FP\); slice len is 8-byte value`
MOVV x_len+8(FP), R19
MOVH x_cap+0(FP), R19 // want `invalid offset x_cap\+0\(FP\); expected x_cap\+16\(FP\)`
MOVW x_cap+0(FP), R19 // want `invalid offset x_cap\+0\(FP\); expected x_cap\+16\(FP\)`
MOVV x_cap+0(FP), R19 // want `invalid offset x_cap\+0\(FP\); expected x_cap\+16\(FP\)`
MOVH x_cap+16(FP), R19 // want `invalid MOVH of x_cap\+16\(FP\); slice cap is 8-byte value`
MOVW x_cap+16(FP), R19 // want `invalid MOVW of x_cap\+16\(FP\); slice cap is 8-byte value`
MOVV x_cap+16(FP), R19
MOVV y+0(FP), R19 // want `invalid offset y\+0\(FP\); expected y\+24\(FP\)`
MOVV y_len+8(FP), R19 // want `invalid offset y_len\+8\(FP\); expected y_len\+32\(FP\)`
MOVV y_cap+16(FP), R19 // want `invalid offset y_cap\+16\(FP\); expected y_cap\+40\(FP\)`
RET
TEXT ·argiface(SB),0,$0-32
MOVH x+0(FP), R19 // want `invalid MOVH of x\+0\(FP\); interface type is 8-byte value`
MOVW x+0(FP), R19 // want `invalid MOVW of x\+0\(FP\); interface type is 8-byte value`
MOVV x+0(FP), R19
MOVH x_type+0(FP), R19 // want `invalid MOVH of x_type\+0\(FP\); interface type is 8-byte value`
MOVW x_type+0(FP), R19 // want `invalid MOVW of x_type\+0\(FP\); interface type is 8-byte value`
MOVV x_type+0(FP), R19
MOVV x_itable+0(FP), R19 // want `unknown variable x_itable; offset 0 is x_type\+0\(FP\)`
MOVV x_itable+1(FP), R19 // want `unknown variable x_itable; offset 1 is x_type\+0\(FP\)`
MOVH x_data+0(FP), R19 // want `invalid offset x_data\+0\(FP\); expected x_data\+8\(FP\)`
MOVW x_data+0(FP), R19 // want `invalid offset x_data\+0\(FP\); expected x_data\+8\(FP\)`
MOVV x_data+0(FP), R19 // want `invalid offset x_data\+0\(FP\); expected x_data\+8\(FP\)`
MOVH x_data+8(FP), R19 // want `invalid MOVH of x_data\+8\(FP\); interface data is 8-byte value`
MOVW x_data+8(FP), R19 // want `invalid MOVW of x_data\+8\(FP\); interface data is 8-byte value`
MOVV x_data+8(FP), R19
MOVH y+16(FP), R19 // want `invalid MOVH of y\+16\(FP\); interface itable is 8-byte value`
MOVW y+16(FP), R19 // want `invalid MOVW of y\+16\(FP\); interface itable is 8-byte value`
MOVV y+16(FP), R19
MOVH y_itable+16(FP), R19 // want `invalid MOVH of y_itable\+16\(FP\); interface itable is 8-byte value`
MOVW y_itable+16(FP), R19 // want `invalid MOVW of y_itable\+16\(FP\); interface itable is 8-byte value`
MOVV y_itable+16(FP), R19
MOVV y_type+16(FP), R19 // want `unknown variable y_type; offset 16 is y_itable\+16\(FP\)`
MOVH y_data+16(FP), R19 // want `invalid offset y_data\+16\(FP\); expected y_data\+24\(FP\)`
MOVW y_data+16(FP), R19 // want `invalid offset y_data\+16\(FP\); expected y_data\+24\(FP\)`
MOVV y_data+16(FP), R19 // want `invalid offset y_data\+16\(FP\); expected y_data\+24\(FP\)`
MOVH y_data+24(FP), R19 // want `invalid MOVH of y_data\+24\(FP\); interface data is 8-byte value`
MOVW y_data+24(FP), R19 // want `invalid MOVW of y_data\+24\(FP\); interface data is 8-byte value`
MOVV y_data+24(FP), R19
RET
TEXT ·returnint(SB),0,$0-8
MOVB R19, ret+0(FP) // want `invalid MOVB of ret\+0\(FP\); int is 8-byte value`
MOVH R19, ret+0(FP) // want `invalid MOVH of ret\+0\(FP\); int is 8-byte value`
MOVW R19, ret+0(FP) // want `invalid MOVW of ret\+0\(FP\); int is 8-byte value`
MOVV R19, ret+0(FP)
MOVV R19, ret+1(FP) // want `invalid offset ret\+1\(FP\); expected ret\+0\(FP\)`
MOVV R19, r+0(FP) // want `unknown variable r; offset 0 is ret\+0\(FP\)`
RET
TEXT ·returnbyte(SB),0,$0-9
MOVV x+0(FP), R19
MOVB R19, ret+8(FP)
MOVH R19, ret+8(FP) // want `invalid MOVH of ret\+8\(FP\); byte is 1-byte value`
MOVW R19, ret+8(FP) // want `invalid MOVW of ret\+8\(FP\); byte is 1-byte value`
MOVV R19, ret+8(FP) // want `invalid MOVV of ret\+8\(FP\); byte is 1-byte value`
MOVB R19, ret+7(FP) // want `invalid offset ret\+7\(FP\); expected ret\+8\(FP\)`
RET
TEXT ·returnnamed(SB),0,$0-41
MOVB x+0(FP), R19
MOVV R19, r1+8(FP)
MOVH R19, r2+16(FP)
MOVV R19, r3+24(FP)
MOVV R19, r3_base+24(FP)
MOVV R19, r3_len+32(FP)
MOVB R19, r4+40(FP)
MOVW R19, r1+8(FP) // want `invalid MOVW of r1\+8\(FP\); int is 8-byte value`
RET
TEXT ·returnintmissing(SB),0,$0-8
RET // want `RET without writing to 8-byte ret\+0\(FP\)`
================================================
FILE: go/analysis/passes/asmdecl/testdata/src/a/asm11.s
================================================
// Copyright 2022 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build riscv64
// writing to result in ABIInternal function
TEXT ·returnABIInternal(SB), NOSPLIT, $8
MOV $123, X10
RET
TEXT ·returnmissingABIInternal(SB), NOSPLIT, $8
MOV $123, X20
RET // want `RET without writing to result register`
// issue 69352
TEXT ·returnsyscallABIInternal(SB), NOSPLIT, $0
MOV $123, X20
ECALL
RET
================================================
FILE: go/analysis/passes/asmdecl/testdata/src/a/asm2.s
================================================
// Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build 386
TEXT ·arg1(SB),0,$0-2
MOVB x+0(FP), AX
MOVB y+1(FP), BX
MOVW x+0(FP), AX // want `\[386\] arg1: invalid MOVW of x\+0\(FP\); int8 is 1-byte value`
MOVW y+1(FP), AX // want `invalid MOVW of y\+1\(FP\); uint8 is 1-byte value`
MOVL x+0(FP), AX // want `invalid MOVL of x\+0\(FP\); int8 is 1-byte value`
MOVL y+1(FP), AX // want `invalid MOVL of y\+1\(FP\); uint8 is 1-byte value`
MOVQ x+0(FP), AX // want `invalid MOVQ of x\+0\(FP\); int8 is 1-byte value`
MOVQ y+1(FP), AX // want `invalid MOVQ of y\+1\(FP\); uint8 is 1-byte value`
MOVB x+1(FP), AX // want `invalid offset x\+1\(FP\); expected x\+0\(FP\)`
MOVB y+2(FP), AX // want `invalid offset y\+2\(FP\); expected y\+1\(FP\)`
TESTB x+0(FP), AX
TESTB y+1(FP), BX
TESTW x+0(FP), AX // want `invalid TESTW of x\+0\(FP\); int8 is 1-byte value`
TESTW y+1(FP), AX // want `invalid TESTW of y\+1\(FP\); uint8 is 1-byte value`
TESTL x+0(FP), AX // want `invalid TESTL of x\+0\(FP\); int8 is 1-byte value`
TESTL y+1(FP), AX // want `invalid TESTL of y\+1\(FP\); uint8 is 1-byte value`
TESTQ x+0(FP), AX // want `invalid TESTQ of x\+0\(FP\); int8 is 1-byte value`
TESTQ y+1(FP), AX // want `invalid TESTQ of y\+1\(FP\); uint8 is 1-byte value`
TESTB x+1(FP), AX // want `invalid offset x\+1\(FP\); expected x\+0\(FP\)`
TESTB y+2(FP), AX // want `invalid offset y\+2\(FP\); expected y\+1\(FP\)`
MOVB 4(SP), AX // want `4\(SP\) should be x\+0\(FP\)`
MOVB 5(SP), AX // want `5\(SP\) should be y\+1\(FP\)`
MOVB 6(SP), AX // want `use of 6\(SP\) points beyond argument frame`
RET
TEXT ·arg2(SB),0,$0-4
MOVB x+0(FP), AX // want `arg2: invalid MOVB of x\+0\(FP\); int16 is 2-byte value`
MOVB y+2(FP), AX // want `invalid MOVB of y\+2\(FP\); uint16 is 2-byte value`
MOVW x+0(FP), AX
MOVW y+2(FP), BX
MOVL x+0(FP), AX // want `invalid MOVL of x\+0\(FP\); int16 is 2-byte value`
MOVL y+2(FP), AX // want `invalid MOVL of y\+2\(FP\); uint16 is 2-byte value`
MOVQ x+0(FP), AX // want `invalid MOVQ of x\+0\(FP\); int16 is 2-byte value`
MOVQ y+2(FP), AX // want `invalid MOVQ of y\+2\(FP\); uint16 is 2-byte value`
MOVW x+2(FP), AX // want `invalid offset x\+2\(FP\); expected x\+0\(FP\)`
MOVW y+0(FP), AX // want `invalid offset y\+0\(FP\); expected y\+2\(FP\)`
TESTB x+0(FP), AX // want `invalid TESTB of x\+0\(FP\); int16 is 2-byte value`
TESTB y+2(FP), AX // want `invalid TESTB of y\+2\(FP\); uint16 is 2-byte value`
TESTW x+0(FP), AX
TESTW y+2(FP), BX
TESTL x+0(FP), AX // want `invalid TESTL of x\+0\(FP\); int16 is 2-byte value`
TESTL y+2(FP), AX // want `invalid TESTL of y\+2\(FP\); uint16 is 2-byte value`
TESTQ x+0(FP), AX // want `invalid TESTQ of x\+0\(FP\); int16 is 2-byte value`
TESTQ y+2(FP), AX // want `invalid TESTQ of y\+2\(FP\); uint16 is 2-byte value`
TESTW x+2(FP), AX // want `invalid offset x\+2\(FP\); expected x\+0\(FP\)`
TESTW y+0(FP), AX // want `invalid offset y\+0\(FP\); expected y\+2\(FP\)`
RET
TEXT ·arg4(SB),0,$0-2 // want `arg4: wrong argument size 2; expected \$\.\.\.-8`
MOVB x+0(FP), AX // want `invalid MOVB of x\+0\(FP\); int32 is 4-byte value`
MOVB y+4(FP), BX // want `invalid MOVB of y\+4\(FP\); uint32 is 4-byte value`
MOVW x+0(FP), AX // want `invalid MOVW of x\+0\(FP\); int32 is 4-byte value`
MOVW y+4(FP), AX // want `invalid MOVW of y\+4\(FP\); uint32 is 4-byte value`
MOVL x+0(FP), AX
MOVL y+4(FP), AX
MOVQ x+0(FP), AX // want `invalid MOVQ of x\+0\(FP\); int32 is 4-byte value`
MOVQ y+4(FP), AX // want `invalid MOVQ of y\+4\(FP\); uint32 is 4-byte value`
MOVL x+4(FP), AX // want `invalid offset x\+4\(FP\); expected x\+0\(FP\)`
MOVL y+2(FP), AX // want `invalid offset y\+2\(FP\); expected y\+4\(FP\)`
TESTB x+0(FP), AX // want `invalid TESTB of x\+0\(FP\); int32 is 4-byte value`
TESTB y+4(FP), BX // want `invalid TESTB of y\+4\(FP\); uint32 is 4-byte value`
TESTW x+0(FP), AX // want `invalid TESTW of x\+0\(FP\); int32 is 4-byte value`
TESTW y+4(FP), AX // want `invalid TESTW of y\+4\(FP\); uint32 is 4-byte value`
TESTL x+0(FP), AX
TESTL y+4(FP), AX
TESTQ x+0(FP), AX // want `invalid TESTQ of x\+0\(FP\); int32 is 4-byte value`
TESTQ y+4(FP), AX // want `invalid TESTQ of y\+4\(FP\); uint32 is 4-byte value`
TESTL x+4(FP), AX // want `invalid offset x\+4\(FP\); expected x\+0\(FP\)`
TESTL y+2(FP), AX // want `invalid offset y\+2\(FP\); expected y\+4\(FP\)`
RET
TEXT ·arg8(SB),7,$0-2 // want `wrong argument size 2; expected \$\.\.\.-16`
MOVB x+0(FP), AX // want `invalid MOVB of x\+0\(FP\); int64 is 8-byte value`
MOVB y+8(FP), BX // want `invalid MOVB of y\+8\(FP\); uint64 is 8-byte value`
MOVW x+0(FP), AX // want `invalid MOVW of x\+0\(FP\); int64 is 8-byte value`
MOVW y+8(FP), AX // want `invalid MOVW of y\+8\(FP\); uint64 is 8-byte value`
MOVL x+0(FP), AX // want `invalid MOVL of x\+0\(FP\); int64 is 8-byte value containing x_lo\+0\(FP\) and x_hi\+4\(FP\)`
MOVL x_lo+0(FP), AX
MOVL x_hi+4(FP), AX
MOVL y+8(FP), AX // want `invalid MOVL of y\+8\(FP\); uint64 is 8-byte value containing y_lo\+8\(FP\) and y_hi\+12\(FP\)`
MOVL y_lo+8(FP), AX
MOVL y_hi+12(FP), AX
MOVQ x+0(FP), AX
MOVQ y+8(FP), AX
MOVQ x+8(FP), AX // want `invalid offset x\+8\(FP\); expected x\+0\(FP\)`
MOVQ y+2(FP), AX // want `invalid offset y\+2\(FP\); expected y\+8\(FP\)`
TESTB x+0(FP), AX // want `invalid TESTB of x\+0\(FP\); int64 is 8-byte value`
TESTB y+8(FP), BX // want `invalid TESTB of y\+8\(FP\); uint64 is 8-byte value`
TESTW x+0(FP), AX // want `invalid TESTW of x\+0\(FP\); int64 is 8-byte value`
TESTW y+8(FP), AX // want `invalid TESTW of y\+8\(FP\); uint64 is 8-byte value`
TESTL x+0(FP), AX // want `invalid TESTL of x\+0\(FP\); int64 is 8-byte value containing x_lo\+0\(FP\) and x_hi\+4\(FP\)`
TESTL y+8(FP), AX // want `invalid TESTL of y\+8\(FP\); uint64 is 8-byte value containing y_lo\+8\(FP\) and y_hi\+12\(FP\)`
TESTQ x+0(FP), AX
TESTQ y+8(FP), AX
TESTQ x+8(FP), AX // want `invalid offset x\+8\(FP\); expected x\+0\(FP\)`
TESTQ y+2(FP), AX // want `invalid offset y\+2\(FP\); expected y\+8\(FP\)`
RET
TEXT ·argint(SB),0,$0-2 // want `wrong argument size 2; expected \$\.\.\.-8`
MOVB x+0(FP), AX // want `invalid MOVB of x\+0\(FP\); int is 4-byte value`
MOVB y+4(FP), BX // want `invalid MOVB of y\+4\(FP\); uint is 4-byte value`
MOVW x+0(FP), AX // want `invalid MOVW of x\+0\(FP\); int is 4-byte value`
MOVW y+4(FP), AX // want `invalid MOVW of y\+4\(FP\); uint is 4-byte value`
MOVL x+0(FP), AX
MOVL y+4(FP), AX
MOVQ x+0(FP), AX // want `invalid MOVQ of x\+0\(FP\); int is 4-byte value`
MOVQ y+4(FP), AX // want `invalid MOVQ of y\+4\(FP\); uint is 4-byte value`
MOVQ x+4(FP), AX // want `invalid offset x\+4\(FP\); expected x\+0\(FP\)`
MOVQ y+2(FP), AX // want `invalid offset y\+2\(FP\); expected y\+4\(FP\)`
TESTB x+0(FP), AX // want `invalid TESTB of x\+0\(FP\); int is 4-byte value`
TESTB y+4(FP), BX // want `invalid TESTB of y\+4\(FP\); uint is 4-byte value`
TESTW x+0(FP), AX // want `invalid TESTW of x\+0\(FP\); int is 4-byte value`
TESTW y+4(FP), AX // want `invalid TESTW of y\+4\(FP\); uint is 4-byte value`
TESTL x+0(FP), AX
TESTL y+4(FP), AX
TESTQ x+0(FP), AX // want `invalid TESTQ of x\+0\(FP\); int is 4-byte value`
TESTQ y+4(FP), AX // want `invalid TESTQ of y\+4\(FP\); uint is 4-byte value`
TESTQ x+4(FP), AX // want `invalid offset x\+4\(FP\); expected x\+0\(FP\)`
TESTQ y+2(FP), AX // want `invalid offset y\+2\(FP\); expected y\+4\(FP\)`
RET
TEXT ·argptr(SB),7,$0-2 // want `wrong argument size 2; expected \$\.\.\.-20`
MOVB x+0(FP), AX // want `invalid MOVB of x\+0\(FP\); \*byte is 4-byte value`
MOVB y+4(FP), BX // want `invalid MOVB of y\+4\(FP\); \*byte is 4-byte value`
MOVW x+0(FP), AX // want `invalid MOVW of x\+0\(FP\); \*byte is 4-byte value`
MOVW y+4(FP), AX // want `invalid MOVW of y\+4\(FP\); \*byte is 4-byte value`
MOVL x+0(FP), AX
MOVL y+4(FP), AX
MOVQ x+0(FP), AX // want `invalid MOVQ of x\+0\(FP\); \*byte is 4-byte value`
MOVQ y+4(FP), AX // want `invalid MOVQ of y\+4\(FP\); \*byte is 4-byte value`
MOVQ x+4(FP), AX // want `invalid offset x\+4\(FP\); expected x\+0\(FP\)`
MOVQ y+2(FP), AX // want `invalid offset y\+2\(FP\); expected y\+4\(FP\)`
TESTB x+0(FP), AX // want `invalid TESTB of x\+0\(FP\); \*byte is 4-byte value`
TESTB y+4(FP), BX // want `invalid TESTB of y\+4\(FP\); \*byte is 4-byte value`
TESTW x+0(FP), AX // want `invalid TESTW of x\+0\(FP\); \*byte is 4-byte value`
TESTW y+4(FP), AX // want `invalid TESTW of y\+4\(FP\); \*byte is 4-byte value`
TESTL x+0(FP), AX
TESTL y+4(FP), AX
TESTQ x+0(FP), AX // want `invalid TESTQ of x\+0\(FP\); \*byte is 4-byte value`
TESTQ y+4(FP), AX // want `invalid TESTQ of y\+4\(FP\); \*byte is 4-byte value`
TESTQ x+4(FP), AX // want `invalid offset x\+4\(FP\); expected x\+0\(FP\)`
TESTQ y+2(FP), AX // want `invalid offset y\+2\(FP\); expected y\+4\(FP\)`
MOVW c+8(FP), AX // want `invalid MOVW of c\+8\(FP\); chan int is 4-byte value`
MOVW m+12(FP), AX // want `invalid MOVW of m\+12\(FP\); map\[int\]int is 4-byte value`
MOVW f+16(FP), AX // want `invalid MOVW of f\+16\(FP\); func\(\) is 4-byte value`
RET
TEXT ·argstring(SB),0,$16 // want `wrong argument size 0; expected \$\.\.\.-16`
MOVW x+0(FP), AX // want `invalid MOVW of x\+0\(FP\); string base is 4-byte value`
MOVL x+0(FP), AX
MOVQ x+0(FP), AX // want `invalid MOVQ of x\+0\(FP\); string base is 4-byte value`
MOVW x_base+0(FP), AX // want `invalid MOVW of x_base\+0\(FP\); string base is 4-byte value`
MOVL x_base+0(FP), AX
MOVQ x_base+0(FP), AX // want `invalid MOVQ of x_base\+0\(FP\); string base is 4-byte value`
MOVW x_len+0(FP), AX // want `invalid offset x_len\+0\(FP\); expected x_len\+4\(FP\)`
MOVL x_len+0(FP), AX // want `invalid offset x_len\+0\(FP\); expected x_len\+4\(FP\)`
MOVQ x_len+0(FP), AX // want `invalid offset x_len\+0\(FP\); expected x_len\+4\(FP\)`
MOVW x_len+4(FP), AX // want `invalid MOVW of x_len\+4\(FP\); string len is 4-byte value`
MOVL x_len+4(FP), AX
MOVQ x_len+4(FP), AX // want `invalid MOVQ of x_len\+4\(FP\); string len is 4-byte value`
MOVQ y+0(FP), AX // want `invalid offset y\+0\(FP\); expected y\+8\(FP\)`
MOVQ y_len+4(FP), AX // want `invalid offset y_len\+4\(FP\); expected y_len\+12\(FP\)`
RET
TEXT ·argslice(SB),0,$24 // want `wrong argument size 0; expected \$\.\.\.-24`
MOVW x+0(FP), AX // want `invalid MOVW of x\+0\(FP\); slice base is 4-byte value`
MOVL x+0(FP), AX
MOVQ x+0(FP), AX // want `invalid MOVQ of x\+0\(FP\); slice base is 4-byte value`
MOVW x_base+0(FP), AX // want `invalid MOVW of x_base\+0\(FP\); slice base is 4-byte value`
MOVL x_base+0(FP), AX
MOVQ x_base+0(FP), AX // want `invalid MOVQ of x_base\+0\(FP\); slice base is 4-byte value`
MOVW x_len+0(FP), AX // want `invalid offset x_len\+0\(FP\); expected x_len\+4\(FP\)`
MOVL x_len+0(FP), AX // want `invalid offset x_len\+0\(FP\); expected x_len\+4\(FP\)`
MOVQ x_len+0(FP), AX // want `invalid offset x_len\+0\(FP\); expected x_len\+4\(FP\)`
MOVW x_len+4(FP), AX // want `invalid MOVW of x_len\+4\(FP\); slice len is 4-byte value`
MOVL x_len+4(FP), AX
MOVQ x_len+4(FP), AX // want `invalid MOVQ of x_len\+4\(FP\); slice len is 4-byte value`
MOVW x_cap+0(FP), AX // want `invalid offset x_cap\+0\(FP\); expected x_cap\+8\(FP\)`
MOVL x_cap+0(FP), AX // want `invalid offset x_cap\+0\(FP\); expected x_cap\+8\(FP\)`
MOVQ x_cap+0(FP), AX // want `invalid offset x_cap\+0\(FP\); expected x_cap\+8\(FP\)`
MOVW x_cap+8(FP), AX // want `invalid MOVW of x_cap\+8\(FP\); slice cap is 4-byte value`
MOVL x_cap+8(FP), AX
MOVQ x_cap+8(FP), AX // want `invalid MOVQ of x_cap\+8\(FP\); slice cap is 4-byte value`
MOVQ y+0(FP), AX // want `invalid offset y\+0\(FP\); expected y\+12\(FP\)`
MOVQ y_len+4(FP), AX // want `invalid offset y_len\+4\(FP\); expected y_len\+16\(FP\)`
MOVQ y_cap+8(FP), AX // want `invalid offset y_cap\+8\(FP\); expected y_cap\+20\(FP\)`
RET
TEXT ·argiface(SB),0,$0-16
MOVW x+0(FP), AX // want `invalid MOVW of x\+0\(FP\); interface type is 4-byte value`
MOVL x+0(FP), AX
MOVQ x+0(FP), AX // want `invalid MOVQ of x\+0\(FP\); interface type is 4-byte value`
MOVW x_type+0(FP), AX // want `invalid MOVW of x_type\+0\(FP\); interface type is 4-byte value`
MOVL x_type+0(FP), AX
MOVQ x_type+0(FP), AX // want `invalid MOVQ of x_type\+0\(FP\); interface type is 4-byte value`
MOVQ x_itable+0(FP), AX // want `unknown variable x_itable; offset 0 is x_type\+0\(FP\)`
MOVQ x_itable+1(FP), AX // want `unknown variable x_itable; offset 1 is x_type\+0\(FP\)`
MOVW x_data+0(FP), AX // want `invalid offset x_data\+0\(FP\); expected x_data\+4\(FP\)`
MOVL x_data+0(FP), AX // want `invalid offset x_data\+0\(FP\); expected x_data\+4\(FP\)`
MOVQ x_data+0(FP), AX // want `invalid offset x_data\+0\(FP\); expected x_data\+4\(FP\)`
MOVW x_data+4(FP), AX // want `invalid MOVW of x_data\+4\(FP\); interface data is 4-byte value`
MOVL x_data+4(FP), AX
MOVQ x_data+4(FP), AX // want `invalid MOVQ of x_data\+4\(FP\); interface data is 4-byte value`
MOVW y+8(FP), AX // want `invalid MOVW of y\+8\(FP\); interface itable is 4-byte value`
MOVL y+8(FP), AX
MOVQ y+8(FP), AX // want `invalid MOVQ of y\+8\(FP\); interface itable is 4-byte value`
MOVW y_itable+8(FP), AX // want `invalid MOVW of y_itable\+8\(FP\); interface itable is 4-byte value`
MOVL y_itable+8(FP), AX
MOVQ y_itable+8(FP), AX // want `invalid MOVQ of y_itable\+8\(FP\); interface itable is 4-byte value`
MOVQ y_type+8(FP), AX // want `unknown variable y_type; offset 8 is y_itable\+8\(FP\)`
MOVW y_data+8(FP), AX // want `invalid offset y_data\+8\(FP\); expected y_data\+12\(FP\)`
MOVL y_data+8(FP), AX // want `invalid offset y_data\+8\(FP\); expected y_data\+12\(FP\)`
MOVQ y_data+8(FP), AX // want `invalid offset y_data\+8\(FP\); expected y_data\+12\(FP\)`
MOVW y_data+12(FP), AX // want `invalid MOVW of y_data\+12\(FP\); interface data is 4-byte value`
MOVL y_data+12(FP), AX
MOVQ y_data+12(FP), AX // want `invalid MOVQ of y_data\+12\(FP\); interface data is 4-byte value`
RET
TEXT ·returnint(SB),0,$0-4
MOVB AX, ret+0(FP) // want `invalid MOVB of ret\+0\(FP\); int is 4-byte value`
MOVW AX, ret+0(FP) // want `invalid MOVW of ret\+0\(FP\); int is 4-byte value`
MOVL AX, ret+0(FP)
MOVQ AX, ret+0(FP) // want `invalid MOVQ of ret\+0\(FP\); int is 4-byte value`
MOVQ AX, ret+1(FP) // want `invalid offset ret\+1\(FP\); expected ret\+0\(FP\)`
MOVQ AX, r+0(FP) // want `unknown variable r; offset 0 is ret\+0\(FP\)`
RET
TEXT ·returnbyte(SB),0,$0-5
MOVL x+0(FP), AX
MOVB AX, ret+4(FP)
MOVW AX, ret+4(FP) // want `invalid MOVW of ret\+4\(FP\); byte is 1-byte value`
MOVL AX, ret+4(FP) // want `invalid MOVL of ret\+4\(FP\); byte is 1-byte value`
MOVQ AX, ret+4(FP) // want `invalid MOVQ of ret\+4\(FP\); byte is 1-byte value`
MOVB AX, ret+3(FP) // want `invalid offset ret\+3\(FP\); expected ret\+4\(FP\)`
RET
TEXT ·returnnamed(SB),0,$0-21
MOVB x+0(FP), AX
MOVL AX, r1+4(FP)
MOVW AX, r2+8(FP)
MOVL AX, r3+12(FP)
MOVL AX, r3_base+12(FP)
MOVL AX, r3_len+16(FP)
MOVB AX, r4+20(FP)
MOVQ AX, r1+4(FP) // want `invalid MOVQ of r1\+4\(FP\); int is 4-byte value`
RET
TEXT ·returnintmissing(SB),0,$0-4
RET // want `RET without writing to 4-byte ret\+0\(FP\)`
================================================
FILE: go/analysis/passes/asmdecl/testdata/src/a/asm3.s
================================================
// Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build arm
TEXT ·arg1(SB),0,$0-2
MOVB x+0(FP), AX
MOVB y+1(FP), BX
MOVH x+0(FP), AX // want `\[arm\] arg1: invalid MOVH of x\+0\(FP\); int8 is 1-byte value`
MOVH y+1(FP), AX // want `invalid MOVH of y\+1\(FP\); uint8 is 1-byte value`
MOVW x+0(FP), AX // want `invalid MOVW of x\+0\(FP\); int8 is 1-byte value`
MOVW y+1(FP), AX // want `invalid MOVW of y\+1\(FP\); uint8 is 1-byte value`
MOVB x+1(FP), AX // want `invalid offset x\+1\(FP\); expected x\+0\(FP\)`
MOVB y+2(FP), AX // want `invalid offset y\+2\(FP\); expected y\+1\(FP\)`
MOVB 8(R13), AX // want `8\(R13\) should be x\+0\(FP\)`
MOVB 9(R13), AX // want `9\(R13\) should be y\+1\(FP\)`
MOVB 10(R13), AX // want `use of 10\(R13\) points beyond argument frame`
RET
TEXT ·arg2(SB),0,$0-4
MOVB x+0(FP), AX // want `arg2: invalid MOVB of x\+0\(FP\); int16 is 2-byte value`
MOVB y+2(FP), AX // want `invalid MOVB of y\+2\(FP\); uint16 is 2-byte value`
MOVH x+0(FP), AX
MOVH y+2(FP), BX
MOVW x+0(FP), AX // want `invalid MOVW of x\+0\(FP\); int16 is 2-byte value`
MOVW y+2(FP), AX // want `invalid MOVW of y\+2\(FP\); uint16 is 2-byte value`
MOVH x+2(FP), AX // want `invalid offset x\+2\(FP\); expected x\+0\(FP\)`
MOVH y+0(FP), AX // want `invalid offset y\+0\(FP\); expected y\+2\(FP\)`
RET
TEXT ·arg4(SB),0,$0-2 // want `arg4: wrong argument size 2; expected \$\.\.\.-8`
MOVB x+0(FP), AX // want `invalid MOVB of x\+0\(FP\); int32 is 4-byte value`
MOVB y+4(FP), BX // want `invalid MOVB of y\+4\(FP\); uint32 is 4-byte value`
MOVH x+0(FP), AX // want `invalid MOVH of x\+0\(FP\); int32 is 4-byte value`
MOVH y+4(FP), AX // want `invalid MOVH of y\+4\(FP\); uint32 is 4-byte value`
MOVW x+0(FP), AX
MOVW y+4(FP), AX
MOVW x+4(FP), AX // want `invalid offset x\+4\(FP\); expected x\+0\(FP\)`
MOVW y+2(FP), AX // want `invalid offset y\+2\(FP\); expected y\+4\(FP\)`
RET
TEXT ·arg8(SB),7,$0-2 // want `wrong argument size 2; expected \$\.\.\.-16`
MOVB x+0(FP), AX // want `invalid MOVB of x\+0\(FP\); int64 is 8-byte value`
MOVB y+8(FP), BX // want `invalid MOVB of y\+8\(FP\); uint64 is 8-byte value`
MOVH x+0(FP), AX // want `invalid MOVH of x\+0\(FP\); int64 is 8-byte value`
MOVH y+8(FP), AX // want `invalid MOVH of y\+8\(FP\); uint64 is 8-byte value`
MOVW x+0(FP), AX // want `invalid MOVW of x\+0\(FP\); int64 is 8-byte value containing x_lo\+0\(FP\) and x_hi\+4\(FP\)`
MOVW x_lo+0(FP), AX
MOVW x_hi+4(FP), AX
MOVW y+8(FP), AX // want `invalid MOVW of y\+8\(FP\); uint64 is 8-byte value containing y_lo\+8\(FP\) and y_hi\+12\(FP\)`
MOVW y_lo+8(FP), AX
MOVW y_hi+12(FP), AX
MOVQ x+0(FP), AX
MOVQ y+8(FP), AX
MOVQ x+8(FP), AX // want `invalid offset x\+8\(FP\); expected x\+0\(FP\)`
MOVQ y+2(FP), AX // want `invalid offset y\+2\(FP\); expected y\+8\(FP\)`
RET
TEXT ·argint(SB),0,$0-2 // want `wrong argument size 2; expected \$\.\.\.-8`
MOVB x+0(FP), AX // want `invalid MOVB of x\+0\(FP\); int is 4-byte value`
MOVB y+4(FP), BX // want `invalid MOVB of y\+4\(FP\); uint is 4-byte value`
MOVH x+0(FP), AX // want `invalid MOVH of x\+0\(FP\); int is 4-byte value`
MOVH y+4(FP), AX // want `invalid MOVH of y\+4\(FP\); uint is 4-byte value`
MOVW x+0(FP), AX
MOVW y+4(FP), AX
MOVQ x+4(FP), AX // want `invalid offset x\+4\(FP\); expected x\+0\(FP\)`
MOVQ y+2(FP), AX // want `invalid offset y\+2\(FP\); expected y\+4\(FP\)`
RET
TEXT ·argptr(SB),7,$0-2 // want `wrong argument size 2; expected \$\.\.\.-20`
MOVB x+0(FP), AX // want `invalid MOVB of x\+0\(FP\); \*byte is 4-byte value`
MOVB y+4(FP), BX // want `invalid MOVB of y\+4\(FP\); \*byte is 4-byte value`
MOVH x+0(FP), AX // want `invalid MOVH of x\+0\(FP\); \*byte is 4-byte value`
MOVH y+4(FP), AX // want `invalid MOVH of y\+4\(FP\); \*byte is 4-byte value`
MOVW x+0(FP), AX
MOVW y+4(FP), AX
MOVQ x+4(FP), AX // want `invalid offset x\+4\(FP\); expected x\+0\(FP\)`
MOVQ y+2(FP), AX // want `invalid offset y\+2\(FP\); expected y\+4\(FP\)`
MOVH c+8(FP), AX // want `invalid MOVH of c\+8\(FP\); chan int is 4-byte value`
MOVH m+12(FP), AX // want `invalid MOVH of m\+12\(FP\); map\[int\]int is 4-byte value`
MOVH f+16(FP), AX // want `invalid MOVH of f\+16\(FP\); func\(\) is 4-byte value`
RET
TEXT ·argstring(SB),0,$16 // want `wrong argument size 0; expected \$\.\.\.-16`
MOVH x+0(FP), AX // want `invalid MOVH of x\+0\(FP\); string base is 4-byte value`
MOVW x+0(FP), AX
MOVH x_base+0(FP), AX // want `invalid MOVH of x_base\+0\(FP\); string base is 4-byte value`
MOVW x_base+0(FP), AX
MOVH x_len+0(FP), AX // want `invalid offset x_len\+0\(FP\); expected x_len\+4\(FP\)`
MOVW x_len+0(FP), AX // want `invalid offset x_len\+0\(FP\); expected x_len\+4\(FP\)`
MOVQ x_len+0(FP), AX // want `invalid offset x_len\+0\(FP\); expected x_len\+4\(FP\)`
MOVH x_len+4(FP), AX // want `invalid MOVH of x_len\+4\(FP\); string len is 4-byte value`
MOVW x_len+4(FP), AX
MOVQ y+0(FP), AX // want `invalid offset y\+0\(FP\); expected y\+8\(FP\)`
MOVQ y_len+4(FP), AX // want `invalid offset y_len\+4\(FP\); expected y_len\+12\(FP\)`
RET
TEXT ·argslice(SB),0,$24 // want `wrong argument size 0; expected \$\.\.\.-24`
MOVH x+0(FP), AX // want `invalid MOVH of x\+0\(FP\); slice base is 4-byte value`
MOVW x+0(FP), AX
MOVH x_base+0(FP), AX // want `invalid MOVH of x_base\+0\(FP\); slice base is 4-byte value`
MOVW x_base+0(FP), AX
MOVH x_len+0(FP), AX // want `invalid offset x_len\+0\(FP\); expected x_len\+4\(FP\)`
MOVW x_len+0(FP), AX // want `invalid offset x_len\+0\(FP\); expected x_len\+4\(FP\)`
MOVQ x_len+0(FP), AX // want `invalid offset x_len\+0\(FP\); expected x_len\+4\(FP\)`
MOVH x_len+4(FP), AX // want `invalid MOVH of x_len\+4\(FP\); slice len is 4-byte value`
MOVW x_len+4(FP), AX
MOVH x_cap+0(FP), AX // want `invalid offset x_cap\+0\(FP\); expected x_cap\+8\(FP\)`
MOVW x_cap+0(FP), AX // want `invalid offset x_cap\+0\(FP\); expected x_cap\+8\(FP\)`
MOVQ x_cap+0(FP), AX // want `invalid offset x_cap\+0\(FP\); expected x_cap\+8\(FP\)`
MOVH x_cap+8(FP), AX // want `invalid MOVH of x_cap\+8\(FP\); slice cap is 4-byte value`
MOVW x_cap+8(FP), AX
MOVQ y+0(FP), AX // want `invalid offset y\+0\(FP\); expected y\+12\(FP\)`
MOVQ y_len+4(FP), AX // want `invalid offset y_len\+4\(FP\); expected y_len\+16\(FP\)`
MOVQ y_cap+8(FP), AX // want `invalid offset y_cap\+8\(FP\); expected y_cap\+20\(FP\)`
RET
TEXT ·argiface(SB),0,$0-16
MOVH x+0(FP), AX // want `invalid MOVH of x\+0\(FP\); interface type is 4-byte value`
MOVW x+0(FP), AX
MOVH x_type+0(FP), AX // want `invalid MOVH of x_type\+0\(FP\); interface type is 4-byte value`
MOVW x_type+0(FP), AX
MOVQ x_itable+0(FP), AX // want `unknown variable x_itable; offset 0 is x_type\+0\(FP\)`
MOVQ x_itable+1(FP), AX // want `unknown variable x_itable; offset 1 is x_type\+0\(FP\)`
MOVH x_data+0(FP), AX // want `invalid offset x_data\+0\(FP\); expected x_data\+4\(FP\)`
MOVW x_data+0(FP), AX // want `invalid offset x_data\+0\(FP\); expected x_data\+4\(FP\)`
MOVQ x_data+0(FP), AX // want `invalid offset x_data\+0\(FP\); expected x_data\+4\(FP\)`
MOVH x_data+4(FP), AX // want `invalid MOVH of x_data\+4\(FP\); interface data is 4-byte value`
MOVW x_data+4(FP), AX
MOVH y+8(FP), AX // want `invalid MOVH of y\+8\(FP\); interface itable is 4-byte value`
MOVW y+8(FP), AX
MOVH y_itable+8(FP), AX // want `invalid MOVH of y_itable\+8\(FP\); interface itable is 4-byte value`
MOVW y_itable+8(FP), AX
MOVQ y_type+8(FP), AX // want `unknown variable y_type; offset 8 is y_itable\+8\(FP\)`
MOVH y_data+8(FP), AX // want `invalid offset y_data\+8\(FP\); expected y_data\+12\(FP\)`
MOVW y_data+8(FP), AX // want `invalid offset y_data\+8\(FP\); expected y_data\+12\(FP\)`
MOVQ y_data+8(FP), AX // want `invalid offset y_data\+8\(FP\); expected y_data\+12\(FP\)`
MOVH y_data+12(FP), AX // want `invalid MOVH of y_data\+12\(FP\); interface data is 4-byte value`
MOVW y_data+12(FP), AX
RET
TEXT ·returnint(SB),0,$0-4
MOVB AX, ret+0(FP) // want `invalid MOVB of ret\+0\(FP\); int is 4-byte value`
MOVH AX, ret+0(FP) // want `invalid MOVH of ret\+0\(FP\); int is 4-byte value`
MOVW AX, ret+0(FP)
MOVQ AX, ret+1(FP) // want `invalid offset ret\+1\(FP\); expected ret\+0\(FP\)`
MOVQ AX, r+0(FP) // want `unknown variable r; offset 0 is ret\+0\(FP\)`
RET
TEXT ·returnbyte(SB),0,$0-5
MOVW x+0(FP), AX
MOVB AX, ret+4(FP)
MOVH AX, ret+4(FP) // want `invalid MOVH of ret\+4\(FP\); byte is 1-byte value`
MOVW AX, ret+4(FP) // want `invalid MOVW of ret\+4\(FP\); byte is 1-byte value`
MOVB AX, ret+3(FP) // want `invalid offset ret\+3\(FP\); expected ret\+4\(FP\)`
RET
TEXT ·returnnamed(SB),0,$0-21
MOVB x+0(FP), AX
MOVW AX, r1+4(FP)
MOVH AX, r2+8(FP)
MOVW AX, r3+12(FP)
MOVW AX, r3_base+12(FP)
MOVW AX, r3_len+16(FP)
MOVB AX, r4+20(FP)
MOVB AX, r1+4(FP) // want `invalid MOVB of r1\+4\(FP\); int is 4-byte value`
RET
TEXT ·returnintmissing(SB),0,$0-4
RET // want `RET without writing to 4-byte ret\+0\(FP\)`
TEXT ·leaf(SB),0,$-4-12
MOVW x+0(FP), AX
MOVW y+4(FP), AX
MOVW AX, ret+8(FP)
RET
TEXT ·noframe1(SB),0,$0-4
MOVW 0(R13), AX // Okay; our saved LR
MOVW 4(R13), AX // Okay; caller's saved LR
MOVW x+8(R13), AX // Okay; x argument
MOVW 12(R13), AX // want `use of 12\(R13\) points beyond argument frame`
RET
TEXT ·noframe2(SB),NOFRAME,$0-4
MOVW 0(R13), AX // Okay; caller's saved LR
MOVW x+4(R13), AX // Okay; x argument
MOVW 8(R13), AX // Okay - NOFRAME is assumed special
MOVW 12(R13), AX // Okay - NOFRAME is assumed special
RET
================================================
FILE: go/analysis/passes/asmdecl/testdata/src/a/asm4.s
================================================
// Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build amd64
// Test cases for symbolic NOSPLIT etc. on TEXT symbols.
TEXT ·noprof(SB),NOPROF,$0-8
RET
TEXT ·dupok(SB),DUPOK,$0-8
RET
TEXT ·nosplit(SB),NOSPLIT,$0
RET
TEXT ·rodata(SB),RODATA,$0-8
RET
TEXT ·noptr(SB),NOPTR|NOSPLIT,$0
RET
TEXT ·wrapper(SB),WRAPPER,$0-8
RET
================================================
FILE: go/analysis/passes/asmdecl/testdata/src/a/asm5.s
================================================
// Copyright 2016 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build mips64
TEXT ·arg1(SB),0,$0-2
MOVB x+0(FP), R1
MOVBU y+1(FP), R2
MOVH x+0(FP), R1 // want `\[mips64\] arg1: invalid MOVH of x\+0\(FP\); int8 is 1-byte value`
MOVHU y+1(FP), R1 // want `invalid MOVHU of y\+1\(FP\); uint8 is 1-byte value`
MOVW x+0(FP), R1 // want `invalid MOVW of x\+0\(FP\); int8 is 1-byte value`
MOVWU y+1(FP), R1 // want `invalid MOVWU of y\+1\(FP\); uint8 is 1-byte value`
MOVV x+0(FP), R1 // want `invalid MOVV of x\+0\(FP\); int8 is 1-byte value`
MOVV y+1(FP), R1 // want `invalid MOVV of y\+1\(FP\); uint8 is 1-byte value`
MOVB x+1(FP), R1 // want `invalid offset x\+1\(FP\); expected x\+0\(FP\)`
MOVBU y+2(FP), R1 // want `invalid offset y\+2\(FP\); expected y\+1\(FP\)`
MOVB 16(R29), R1 // want `16\(R29\) should be x\+0\(FP\)`
MOVB 17(R29), R1 // want `17\(R29\) should be y\+1\(FP\)`
MOVB 18(R29), R1 // want `use of 18\(R29\) points beyond argument frame`
RET
TEXT ·arg2(SB),0,$0-4
MOVBU x+0(FP), R1 // want `arg2: invalid MOVBU of x\+0\(FP\); int16 is 2-byte value`
MOVB y+2(FP), R1 // want `invalid MOVB of y\+2\(FP\); uint16 is 2-byte value`
MOVHU x+0(FP), R1
MOVH y+2(FP), R2
MOVWU x+0(FP), R1 // want `invalid MOVWU of x\+0\(FP\); int16 is 2-byte value`
MOVW y+2(FP), R1 // want `invalid MOVW of y\+2\(FP\); uint16 is 2-byte value`
MOVV x+0(FP), R1 // want `invalid MOVV of x\+0\(FP\); int16 is 2-byte value`
MOVV y+2(FP), R1 // want `invalid MOVV of y\+2\(FP\); uint16 is 2-byte value`
MOVHU x+2(FP), R1 // want `invalid offset x\+2\(FP\); expected x\+0\(FP\)`
MOVH y+0(FP), R1 // want `invalid offset y\+0\(FP\); expected y\+2\(FP\)`
RET
TEXT ·arg4(SB),0,$0-2 // want `arg4: wrong argument size 2; expected \$\.\.\.-8`
MOVB x+0(FP), R1 // want `invalid MOVB of x\+0\(FP\); int32 is 4-byte value`
MOVB y+4(FP), R2 // want `invalid MOVB of y\+4\(FP\); uint32 is 4-byte value`
MOVH x+0(FP), R1 // want `invalid MOVH of x\+0\(FP\); int32 is 4-byte value`
MOVH y+4(FP), R1 // want `invalid MOVH of y\+4\(FP\); uint32 is 4-byte value`
MOVW x+0(FP), R1
MOVW y+4(FP), R1
MOVV x+0(FP), R1 // want `invalid MOVV of x\+0\(FP\); int32 is 4-byte value`
MOVV y+4(FP), R1 // want `invalid MOVV of y\+4\(FP\); uint32 is 4-byte value`
MOVW x+4(FP), R1 // want `invalid offset x\+4\(FP\); expected x\+0\(FP\)`
MOVW y+2(FP), R1 // want `invalid offset y\+2\(FP\); expected y\+4\(FP\)`
RET
TEXT ·arg8(SB),7,$0-2 // want `wrong argument size 2; expected \$\.\.\.-16`
MOVB x+0(FP), R1 // want `invalid MOVB of x\+0\(FP\); int64 is 8-byte value`
MOVB y+8(FP), R2 // want `invalid MOVB of y\+8\(FP\); uint64 is 8-byte value`
MOVH x+0(FP), R1 // want `invalid MOVH of x\+0\(FP\); int64 is 8-byte value`
MOVH y+8(FP), R1 // want `invalid MOVH of y\+8\(FP\); uint64 is 8-byte value`
MOVW x+0(FP), R1 // want `invalid MOVW of x\+0\(FP\); int64 is 8-byte value`
MOVW y+8(FP), R1 // want `invalid MOVW of y\+8\(FP\); uint64 is 8-byte value`
MOVV x+0(FP), R1
MOVV y+8(FP), R1
MOVV x+8(FP), R1 // want `invalid offset x\+8\(FP\); expected x\+0\(FP\)`
MOVV y+2(FP), R1 // want `invalid offset y\+2\(FP\); expected y\+8\(FP\)`
RET
TEXT ·argint(SB),0,$0-2 // want `wrong argument size 2; expected \$\.\.\.-16`
MOVB x+0(FP), R1 // want `invalid MOVB of x\+0\(FP\); int is 8-byte value`
MOVB y+8(FP), R2 // want `invalid MOVB of y\+8\(FP\); uint is 8-byte value`
MOVH x+0(FP), R1 // want `invalid MOVH of x\+0\(FP\); int is 8-byte value`
MOVH y+8(FP), R1 // want `invalid MOVH of y\+8\(FP\); uint is 8-byte value`
MOVW x+0(FP), R1 // want `invalid MOVW of x\+0\(FP\); int is 8-byte value`
MOVW y+8(FP), R1 // want `invalid MOVW of y\+8\(FP\); uint is 8-byte value`
MOVV x+0(FP), R1
MOVV y+8(FP), R1
MOVV x+8(FP), R1 // want `invalid offset x\+8\(FP\); expected x\+0\(FP\)`
MOVV y+2(FP), R1 // want `invalid offset y\+2\(FP\); expected y\+8\(FP\)`
RET
TEXT ·argptr(SB),7,$0-2 // want `wrong argument size 2; expected \$\.\.\.-40`
MOVB x+0(FP), R1 // want `invalid MOVB of x\+0\(FP\); \*byte is 8-byte value`
MOVB y+8(FP), R2 // want `invalid MOVB of y\+8\(FP\); \*byte is 8-byte value`
MOVH x+0(FP), R1 // want `invalid MOVH of x\+0\(FP\); \*byte is 8-byte value`
MOVH y+8(FP), R1 // want `invalid MOVH of y\+8\(FP\); \*byte is 8-byte value`
MOVW x+0(FP), R1 // want `invalid MOVW of x\+0\(FP\); \*byte is 8-byte value`
MOVW y+8(FP), R1 // want `invalid MOVW of y\+8\(FP\); \*byte is 8-byte value`
MOVV x+0(FP), R1
MOVV y+8(FP), R1
MOVV x+8(FP), R1 // want `invalid offset x\+8\(FP\); expected x\+0\(FP\)`
MOVV y+2(FP), R1 // want `invalid offset y\+2\(FP\); expected y\+8\(FP\)`
MOVW c+16(FP), R1 // want `invalid MOVW of c\+16\(FP\); chan int is 8-byte value`
MOVW m+24(FP), R1 // want `invalid MOVW of m\+24\(FP\); map\[int\]int is 8-byte value`
MOVW f+32(FP), R1 // want `invalid MOVW of f\+32\(FP\); func\(\) is 8-byte value`
RET
TEXT ·argstring(SB),0,$32 // want `wrong argument size 0; expected \$\.\.\.-32`
MOVH x+0(FP), R1 // want `invalid MOVH of x\+0\(FP\); string base is 8-byte value`
MOVW x+0(FP), R1 // want `invalid MOVW of x\+0\(FP\); string base is 8-byte value`
MOVV x+0(FP), R1
MOVH x_base+0(FP), R1 // want `invalid MOVH of x_base\+0\(FP\); string base is 8-byte value`
MOVW x_base+0(FP), R1 // want `invalid MOVW of x_base\+0\(FP\); string base is 8-byte value`
MOVV x_base+0(FP), R1
MOVH x_len+0(FP), R1 // want `invalid offset x_len\+0\(FP\); expected x_len\+8\(FP\)`
MOVW x_len+0(FP), R1 // want `invalid offset x_len\+0\(FP\); expected x_len\+8\(FP\)`
MOVV x_len+0(FP), R1 // want `invalid offset x_len\+0\(FP\); expected x_len\+8\(FP\)`
MOVH x_len+8(FP), R1 // want `invalid MOVH of x_len\+8\(FP\); string len is 8-byte value`
MOVW x_len+8(FP), R1 // want `invalid MOVW of x_len\+8\(FP\); string len is 8-byte value`
MOVV x_len+8(FP), R1
MOVV y+0(FP), R1 // want `invalid offset y\+0\(FP\); expected y\+16\(FP\)`
MOVV y_len+8(FP), R1 // want `invalid offset y_len\+8\(FP\); expected y_len\+24\(FP\)`
RET
TEXT ·argslice(SB),0,$48 // want `wrong argument size 0; expected \$\.\.\.-48`
MOVH x+0(FP), R1 // want `invalid MOVH of x\+0\(FP\); slice base is 8-byte value`
MOVW x+0(FP), R1 // want `invalid MOVW of x\+0\(FP\); slice base is 8-byte value`
MOVV x+0(FP), R1
MOVH x_base+0(FP), R1 // want `invalid MOVH of x_base\+0\(FP\); slice base is 8-byte value`
MOVW x_base+0(FP), R1 // want `invalid MOVW of x_base\+0\(FP\); slice base is 8-byte value`
MOVV x_base+0(FP), R1
MOVH x_len+0(FP), R1 // want `invalid offset x_len\+0\(FP\); expected x_len\+8\(FP\)`
MOVW x_len+0(FP), R1 // want `invalid offset x_len\+0\(FP\); expected x_len\+8\(FP\)`
MOVV x_len+0(FP), R1 // want `invalid offset x_len\+0\(FP\); expected x_len\+8\(FP\)`
MOVH x_len+8(FP), R1 // want `invalid MOVH of x_len\+8\(FP\); slice len is 8-byte value`
MOVW x_len+8(FP), R1 // want `invalid MOVW of x_len\+8\(FP\); slice len is 8-byte value`
MOVV x_len+8(FP), R1
MOVH x_cap+0(FP), R1 // want `invalid offset x_cap\+0\(FP\); expected x_cap\+16\(FP\)`
MOVW x_cap+0(FP), R1 // want `invalid offset x_cap\+0\(FP\); expected x_cap\+16\(FP\)`
MOVV x_cap+0(FP), R1 // want `invalid offset x_cap\+0\(FP\); expected x_cap\+16\(FP\)`
MOVH x_cap+16(FP), R1 // want `invalid MOVH of x_cap\+16\(FP\); slice cap is 8-byte value`
MOVW x_cap+16(FP), R1 // want `invalid MOVW of x_cap\+16\(FP\); slice cap is 8-byte value`
MOVV x_cap+16(FP), R1
MOVV y+0(FP), R1 // want `invalid offset y\+0\(FP\); expected y\+24\(FP\)`
MOVV y_len+8(FP), R1 // want `invalid offset y_len\+8\(FP\); expected y_len\+32\(FP\)`
MOVV y_cap+16(FP), R1 // want `invalid offset y_cap\+16\(FP\); expected y_cap\+40\(FP\)`
RET
TEXT ·argiface(SB),0,$0-32
MOVH x+0(FP), R1 // want `invalid MOVH of x\+0\(FP\); interface type is 8-byte value`
MOVW x+0(FP), R1 // want `invalid MOVW of x\+0\(FP\); interface type is 8-byte value`
MOVV x+0(FP), R1
MOVH x_type+0(FP), R1 // want `invalid MOVH of x_type\+0\(FP\); interface type is 8-byte value`
MOVW x_type+0(FP), R1 // want `invalid MOVW of x_type\+0\(FP\); interface type is 8-byte value`
MOVV x_type+0(FP), R1
MOVV x_itable+0(FP), R1 // want `unknown variable x_itable; offset 0 is x_type\+0\(FP\)`
MOVV x_itable+1(FP), R1 // want `unknown variable x_itable; offset 1 is x_type\+0\(FP\)`
MOVH x_data+0(FP), R1 // want `invalid offset x_data\+0\(FP\); expected x_data\+8\(FP\)`
MOVW x_data+0(FP), R1 // want `invalid offset x_data\+0\(FP\); expected x_data\+8\(FP\)`
MOVV x_data+0(FP), R1 // want `invalid offset x_data\+0\(FP\); expected x_data\+8\(FP\)`
MOVH x_data+8(FP), R1 // want `invalid MOVH of x_data\+8\(FP\); interface data is 8-byte value`
MOVW x_data+8(FP), R1 // want `invalid MOVW of x_data\+8\(FP\); interface data is 8-byte value`
MOVV x_data+8(FP), R1
MOVH y+16(FP), R1 // want `invalid MOVH of y\+16\(FP\); interface itable is 8-byte value`
MOVW y+16(FP), R1 // want `invalid MOVW of y\+16\(FP\); interface itable is 8-byte value`
MOVV y+16(FP), R1
MOVH y_itable+16(FP), R1 // want `invalid MOVH of y_itable\+16\(FP\); interface itable is 8-byte value`
MOVW y_itable+16(FP), R1 // want `invalid MOVW of y_itable\+16\(FP\); interface itable is 8-byte value`
MOVV y_itable+16(FP), R1
MOVV y_type+16(FP), R1 // want `unknown variable y_type; offset 16 is y_itable\+16\(FP\)`
MOVH y_data+16(FP), R1 // want `invalid offset y_data\+16\(FP\); expected y_data\+24\(FP\)`
MOVW y_data+16(FP), R1 // want `invalid offset y_data\+16\(FP\); expected y_data\+24\(FP\)`
MOVV y_data+16(FP), R1 // want `invalid offset y_data\+16\(FP\); expected y_data\+24\(FP\)`
MOVH y_data+24(FP), R1 // want `invalid MOVH of y_data\+24\(FP\); interface data is 8-byte value`
MOVW y_data+24(FP), R1 // want `invalid MOVW of y_data\+24\(FP\); interface data is 8-byte value`
MOVV y_data+24(FP), R1
RET
TEXT ·returnint(SB),0,$0-8
MOVB R1, ret+0(FP) // want `invalid MOVB of ret\+0\(FP\); int is 8-byte value`
MOVH R1, ret+0(FP) // want `invalid MOVH of ret\+0\(FP\); int is 8-byte value`
MOVW R1, ret+0(FP) // want `invalid MOVW of ret\+0\(FP\); int is 8-byte value`
MOVV R1, ret+0(FP)
MOVV R1, ret+1(FP) // want `invalid offset ret\+1\(FP\); expected ret\+0\(FP\)`
MOVV R1, r+0(FP) // want `unknown variable r; offset 0 is ret\+0\(FP\)`
RET
TEXT ·returnbyte(SB),0,$0-9
MOVV x+0(FP), R1
MOVB R1, ret+8(FP)
MOVH R1, ret+8(FP) // want `invalid MOVH of ret\+8\(FP\); byte is 1-byte value`
MOVW R1, ret+8(FP) // want `invalid MOVW of ret\+8\(FP\); byte is 1-byte value`
MOVV R1, ret+8(FP) // want `invalid MOVV of ret\+8\(FP\); byte is 1-byte value`
MOVB R1, ret+7(FP) // want `invalid offset ret\+7\(FP\); expected ret\+8\(FP\)`
RET
TEXT ·returnnamed(SB),0,$0-41
MOVB x+0(FP), R1
MOVV R1, r1+8(FP)
MOVH R1, r2+16(FP)
MOVV R1, r3+24(FP)
MOVV R1, r3_base+24(FP)
MOVV R1, r3_len+32(FP)
MOVB R1, r4+40(FP)
MOVW R1, r1+8(FP) // want `invalid MOVW of r1\+8\(FP\); int is 8-byte value`
RET
TEXT ·returnintmissing(SB),0,$0-8
RET // want `RET without writing to 8-byte ret\+0\(FP\)`
================================================
FILE: go/analysis/passes/asmdecl/testdata/src/a/asm6.s
================================================
// Copyright 2016 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build s390x
TEXT ·arg1(SB),0,$0-2
MOVB x+0(FP), R1
MOVBZ y+1(FP), R2
MOVH x+0(FP), R1 // want `\[s390x\] arg1: invalid MOVH of x\+0\(FP\); int8 is 1-byte value`
MOVHZ y+1(FP), R1 // want `invalid MOVHZ of y\+1\(FP\); uint8 is 1-byte value`
MOVW x+0(FP), R1 // want `invalid MOVW of x\+0\(FP\); int8 is 1-byte value`
MOVWZ y+1(FP), R1 // want `invalid MOVWZ of y\+1\(FP\); uint8 is 1-byte value`
MOVD x+0(FP), R1 // want `invalid MOVD of x\+0\(FP\); int8 is 1-byte value`
MOVD y+1(FP), R1 // want `invalid MOVD of y\+1\(FP\); uint8 is 1-byte value`
MOVB x+1(FP), R1 // want `invalid offset x\+1\(FP\); expected x\+0\(FP\)`
MOVBZ y+2(FP), R1 // want `invalid offset y\+2\(FP\); expected y\+1\(FP\)`
MOVB 16(R15), R1 // want `16\(R15\) should be x\+0\(FP\)`
MOVB 17(R15), R1 // want `17\(R15\) should be y\+1\(FP\)`
MOVB 18(R15), R1 // want `use of 18\(R15\) points beyond argument frame`
RET
TEXT ·arg2(SB),0,$0-4
MOVBZ x+0(FP), R1 // want `arg2: invalid MOVBZ of x\+0\(FP\); int16 is 2-byte value`
MOVB y+2(FP), R1 // want `invalid MOVB of y\+2\(FP\); uint16 is 2-byte value`
MOVHZ x+0(FP), R1
MOVH y+2(FP), R2
MOVWZ x+0(FP), R1 // want `invalid MOVWZ of x\+0\(FP\); int16 is 2-byte value`
MOVW y+2(FP), R1 // want `invalid MOVW of y\+2\(FP\); uint16 is 2-byte value`
MOVD x+0(FP), R1 // want `invalid MOVD of x\+0\(FP\); int16 is 2-byte value`
MOVD y+2(FP), R1 // want `invalid MOVD of y\+2\(FP\); uint16 is 2-byte value`
MOVHZ x+2(FP), R1 // want `invalid offset x\+2\(FP\); expected x\+0\(FP\)`
MOVH y+0(FP), R1 // want `invalid offset y\+0\(FP\); expected y\+2\(FP\)`
RET
TEXT ·arg4(SB),0,$0-2 // want `arg4: wrong argument size 2; expected \$\.\.\.-8`
MOVB x+0(FP), R1 // want `invalid MOVB of x\+0\(FP\); int32 is 4-byte value`
MOVB y+4(FP), R2 // want `invalid MOVB of y\+4\(FP\); uint32 is 4-byte value`
MOVH x+0(FP), R1 // want `invalid MOVH of x\+0\(FP\); int32 is 4-byte value`
MOVH y+4(FP), R1 // want `invalid MOVH of y\+4\(FP\); uint32 is 4-byte value`
MOVW x+0(FP), R1
MOVW y+4(FP), R1
MOVD x+0(FP), R1 // want `invalid MOVD of x\+0\(FP\); int32 is 4-byte value`
MOVD y+4(FP), R1 // want `invalid MOVD of y\+4\(FP\); uint32 is 4-byte value`
MOVW x+4(FP), R1 // want `invalid offset x\+4\(FP\); expected x\+0\(FP\)`
MOVW y+2(FP), R1 // want `invalid offset y\+2\(FP\); expected y\+4\(FP\)`
RET
TEXT ·arg8(SB),7,$0-2 // want `wrong argument size 2; expected \$\.\.\.-16`
MOVB x+0(FP), R1 // want `invalid MOVB of x\+0\(FP\); int64 is 8-byte value`
MOVB y+8(FP), R2 // want `invalid MOVB of y\+8\(FP\); uint64 is 8-byte value`
MOVH x+0(FP), R1 // want `invalid MOVH of x\+0\(FP\); int64 is 8-byte value`
MOVH y+8(FP), R1 // want `invalid MOVH of y\+8\(FP\); uint64 is 8-byte value`
MOVW x+0(FP), R1 // want `invalid MOVW of x\+0\(FP\); int64 is 8-byte value`
MOVW y+8(FP), R1 // want `invalid MOVW of y\+8\(FP\); uint64 is 8-byte value`
MOVD x+0(FP), R1
MOVD y+8(FP), R1
MOVD x+8(FP), R1 // want `invalid offset x\+8\(FP\); expected x\+0\(FP\)`
MOVD y+2(FP), R1 // want `invalid offset y\+2\(FP\); expected y\+8\(FP\)`
RET
TEXT ·argint(SB),0,$0-2 // want `wrong argument size 2; expected \$\.\.\.-16`
MOVB x+0(FP), R1 // want `invalid MOVB of x\+0\(FP\); int is 8-byte value`
MOVB y+8(FP), R2 // want `invalid MOVB of y\+8\(FP\); uint is 8-byte value`
MOVH x+0(FP), R1 // want `invalid MOVH of x\+0\(FP\); int is 8-byte value`
MOVH y+8(FP), R1 // want `invalid MOVH of y\+8\(FP\); uint is 8-byte value`
MOVW x+0(FP), R1 // want `invalid MOVW of x\+0\(FP\); int is 8-byte value`
MOVW y+8(FP), R1 // want `invalid MOVW of y\+8\(FP\); uint is 8-byte value`
MOVD x+0(FP), R1
MOVD y+8(FP), R1
MOVD x+8(FP), R1 // want `invalid offset x\+8\(FP\); expected x\+0\(FP\)`
MOVD y+2(FP), R1 // want `invalid offset y\+2\(FP\); expected y\+8\(FP\)`
RET
TEXT ·argptr(SB),7,$0-2 // want `wrong argument size 2; expected \$\.\.\.-40`
MOVB x+0(FP), R1 // want `invalid MOVB of x\+0\(FP\); \*byte is 8-byte value`
MOVB y+8(FP), R2 // want `invalid MOVB of y\+8\(FP\); \*byte is 8-byte value`
MOVH x+0(FP), R1 // want `invalid MOVH of x\+0\(FP\); \*byte is 8-byte value`
MOVH y+8(FP), R1 // want `invalid MOVH of y\+8\(FP\); \*byte is 8-byte value`
MOVW x+0(FP), R1 // want `invalid MOVW of x\+0\(FP\); \*byte is 8-byte value`
MOVW y+8(FP), R1 // want `invalid MOVW of y\+8\(FP\); \*byte is 8-byte value`
MOVD x+0(FP), R1
MOVD y+8(FP), R1
MOVD x+8(FP), R1 // want `invalid offset x\+8\(FP\); expected x\+0\(FP\)`
MOVD y+2(FP), R1 // want `invalid offset y\+2\(FP\); expected y\+8\(FP\)`
MOVW c+16(FP), R1 // want `invalid MOVW of c\+16\(FP\); chan int is 8-byte value`
MOVW m+24(FP), R1 // want `invalid MOVW of m\+24\(FP\); map\[int\]int is 8-byte value`
MOVW f+32(FP), R1 // want `invalid MOVW of f\+32\(FP\); func\(\) is 8-byte value`
RET
TEXT ·argstring(SB),0,$32 // want `wrong argument size 0; expected \$\.\.\.-32`
MOVH x+0(FP), R1 // want `invalid MOVH of x\+0\(FP\); string base is 8-byte value`
MOVW x+0(FP), R1 // want `invalid MOVW of x\+0\(FP\); string base is 8-byte value`
MOVD x+0(FP), R1
MOVH x_base+0(FP), R1 // want `invalid MOVH of x_base\+0\(FP\); string base is 8-byte value`
MOVW x_base+0(FP), R1 // want `invalid MOVW of x_base\+0\(FP\); string base is 8-byte value`
MOVD x_base+0(FP), R1
MOVH x_len+0(FP), R1 // want `invalid offset x_len\+0\(FP\); expected x_len\+8\(FP\)`
MOVW x_len+0(FP), R1 // want `invalid offset x_len\+0\(FP\); expected x_len\+8\(FP\)`
MOVD x_len+0(FP), R1 // want `invalid offset x_len\+0\(FP\); expected x_len\+8\(FP\)`
MOVH x_len+8(FP), R1 // want `invalid MOVH of x_len\+8\(FP\); string len is 8-byte value`
MOVW x_len+8(FP), R1 // want `invalid MOVW of x_len\+8\(FP\); string len is 8-byte value`
MOVD x_len+8(FP), R1
MOVD y+0(FP), R1 // want `invalid offset y\+0\(FP\); expected y\+16\(FP\)`
MOVD y_len+8(FP), R1 // want `invalid offset y_len\+8\(FP\); expected y_len\+24\(FP\)`
RET
TEXT ·argslice(SB),0,$48 // want `wrong argument size 0; expected \$\.\.\.-48`
MOVH x+0(FP), R1 // want `invalid MOVH of x\+0\(FP\); slice base is 8-byte value`
MOVW x+0(FP), R1 // want `invalid MOVW of x\+0\(FP\); slice base is 8-byte value`
MOVD x+0(FP), R1
MOVH x_base+0(FP), R1 // want `invalid MOVH of x_base\+0\(FP\); slice base is 8-byte value`
MOVW x_base+0(FP), R1 // want `invalid MOVW of x_base\+0\(FP\); slice base is 8-byte value`
MOVD x_base+0(FP), R1
MOVH x_len+0(FP), R1 // want `invalid offset x_len\+0\(FP\); expected x_len\+8\(FP\)`
MOVW x_len+0(FP), R1 // want `invalid offset x_len\+0\(FP\); expected x_len\+8\(FP\)`
MOVD x_len+0(FP), R1 // want `invalid offset x_len\+0\(FP\); expected x_len\+8\(FP\)`
MOVH x_len+8(FP), R1 // want `invalid MOVH of x_len\+8\(FP\); slice len is 8-byte value`
MOVW x_len+8(FP), R1 // want `invalid MOVW of x_len\+8\(FP\); slice len is 8-byte value`
MOVD x_len+8(FP), R1
MOVH x_cap+0(FP), R1 // want `invalid offset x_cap\+0\(FP\); expected x_cap\+16\(FP\)`
MOVW x_cap+0(FP), R1 // want `invalid offset x_cap\+0\(FP\); expected x_cap\+16\(FP\)`
MOVD x_cap+0(FP), R1 // want `invalid offset x_cap\+0\(FP\); expected x_cap\+16\(FP\)`
MOVH x_cap+16(FP), R1 // want `invalid MOVH of x_cap\+16\(FP\); slice cap is 8-byte value`
MOVW x_cap+16(FP), R1 // want `invalid MOVW of x_cap\+16\(FP\); slice cap is 8-byte value`
MOVD x_cap+16(FP), R1
MOVD y+0(FP), R1 // want `invalid offset y\+0\(FP\); expected y\+24\(FP\)`
MOVD y_len+8(FP), R1 // want `invalid offset y_len\+8\(FP\); expected y_len\+32\(FP\)`
MOVD y_cap+16(FP), R1 // want `invalid offset y_cap\+16\(FP\); expected y_cap\+40\(FP\)`
RET
TEXT ·argiface(SB),0,$0-32
MOVH x+0(FP), R1 // want `invalid MOVH of x\+0\(FP\); interface type is 8-byte value`
MOVW x+0(FP), R1 // want `invalid MOVW of x\+0\(FP\); interface type is 8-byte value`
MOVD x+0(FP), R1
MOVH x_type+0(FP), R1 // want `invalid MOVH of x_type\+0\(FP\); interface type is 8-byte value`
MOVW x_type+0(FP), R1 // want `invalid MOVW of x_type\+0\(FP\); interface type is 8-byte value`
MOVD x_type+0(FP), R1
MOVD x_itable+0(FP), R1 // want `unknown variable x_itable; offset 0 is x_type\+0\(FP\)`
MOVD x_itable+1(FP), R1 // want `unknown variable x_itable; offset 1 is x_type\+0\(FP\)`
MOVH x_data+0(FP), R1 // want `invalid offset x_data\+0\(FP\); expected x_data\+8\(FP\)`
MOVW x_data+0(FP), R1 // want `invalid offset x_data\+0\(FP\); expected x_data\+8\(FP\)`
MOVD x_data+0(FP), R1 // want `invalid offset x_data\+0\(FP\); expected x_data\+8\(FP\)`
MOVH x_data+8(FP), R1 // want `invalid MOVH of x_data\+8\(FP\); interface data is 8-byte value`
MOVW x_data+8(FP), R1 // want `invalid MOVW of x_data\+8\(FP\); interface data is 8-byte value`
MOVD x_data+8(FP), R1
MOVH y+16(FP), R1 // want `invalid MOVH of y\+16\(FP\); interface itable is 8-byte value`
MOVW y+16(FP), R1 // want `invalid MOVW of y\+16\(FP\); interface itable is 8-byte value`
MOVD y+16(FP), R1
MOVH y_itable+16(FP), R1 // want `invalid MOVH of y_itable\+16\(FP\); interface itable is 8-byte value`
MOVW y_itable+16(FP), R1 // want `invalid MOVW of y_itable\+16\(FP\); interface itable is 8-byte value`
MOVD y_itable+16(FP), R1
MOVD y_type+16(FP), R1 // want `unknown variable y_type; offset 16 is y_itable\+16\(FP\)`
MOVH y_data+16(FP), R1 // want `invalid offset y_data\+16\(FP\); expected y_data\+24\(FP\)`
MOVW y_data+16(FP), R1 // want `invalid offset y_data\+16\(FP\); expected y_data\+24\(FP\)`
MOVD y_data+16(FP), R1 // want `invalid offset y_data\+16\(FP\); expected y_data\+24\(FP\)`
MOVH y_data+24(FP), R1 // want `invalid MOVH of y_data\+24\(FP\); interface data is 8-byte value`
MOVW y_data+24(FP), R1 // want `invalid MOVW of y_data\+24\(FP\); interface data is 8-byte value`
MOVD y_data+24(FP), R1
RET
TEXT ·returnint(SB),0,$0-8
MOVB R1, ret+0(FP) // want `invalid MOVB of ret\+0\(FP\); int is 8-byte value`
MOVH R1, ret+0(FP) // want `invalid MOVH of ret\+0\(FP\); int is 8-byte value`
MOVW R1, ret+0(FP) // want `invalid MOVW of ret\+0\(FP\); int is 8-byte value`
MOVD R1, ret+0(FP)
MOVD R1, ret+1(FP) // want `invalid offset ret\+1\(FP\); expected ret\+0\(FP\)`
MOVD R1, r+0(FP) // want `unknown variable r; offset 0 is ret\+0\(FP\)`
RET
TEXT ·returnbyte(SB),0,$0-9
MOVD x+0(FP), R1
MOVB R1, ret+8(FP)
MOVH R1, ret+8(FP) // want `invalid MOVH of ret\+8\(FP\); byte is 1-byte value`
MOVW R1, ret+8(FP) // want `invalid MOVW of ret\+8\(FP\); byte is 1-byte value`
MOVD R1, ret+8(FP) // want `invalid MOVD of ret\+8\(FP\); byte is 1-byte value`
MOVB R1, ret+7(FP) // want `invalid offset ret\+7\(FP\); expected ret\+8\(FP\)`
RET
TEXT ·returnnamed(SB),0,$0-41
MOVB x+0(FP), R1
MOVD R1, r1+8(FP)
MOVH R1, r2+16(FP)
MOVD R1, r3+24(FP)
MOVD R1, r3_base+24(FP)
MOVD R1, r3_len+32(FP)
MOVB R1, r4+40(FP)
MOVW R1, r1+8(FP) // want `invalid MOVW of r1\+8\(FP\); int is 8-byte value`
RET
TEXT ·returnintmissing(SB),0,$0-8
RET // want `RET without writing to 8-byte ret\+0\(FP\)`
================================================
FILE: go/analysis/passes/asmdecl/testdata/src/a/asm7.s
================================================
// Copyright 2016 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build ppc64 ppc64le
TEXT ·arg1(SB),0,$0-2
MOVB x+0(FP), R3
MOVBZ y+1(FP), R4
MOVH x+0(FP), R3 // want `\[(ppc64|ppc64le)\] arg1: invalid MOVH of x\+0\(FP\); int8 is 1-byte value`
MOVHZ y+1(FP), R3 // want `invalid MOVHZ of y\+1\(FP\); uint8 is 1-byte value`
MOVW x+0(FP), R3 // want `invalid MOVW of x\+0\(FP\); int8 is 1-byte value`
MOVWZ y+1(FP), R3 // want `invalid MOVWZ of y\+1\(FP\); uint8 is 1-byte value`
MOVD x+0(FP), R3 // want `invalid MOVD of x\+0\(FP\); int8 is 1-byte value`
MOVD y+1(FP), R3 // want `invalid MOVD of y\+1\(FP\); uint8 is 1-byte value`
MOVB x+1(FP), R3 // want `invalid offset x\+1\(FP\); expected x\+0\(FP\)`
MOVBZ y+2(FP), R3 // want `invalid offset y\+2\(FP\); expected y\+1\(FP\)`
MOVB 16(R1), R3 // want `16\(R1\) should be x\+0\(FP\)`
MOVB 17(R1), R3 // want `17\(R1\) should be y\+1\(FP\)`
MOVB 18(R1), R3 // want `use of 18\(R1\) points beyond argument frame`
RET
TEXT ·arg2(SB),0,$0-4
MOVBZ x+0(FP), R3 // want `arg2: invalid MOVBZ of x\+0\(FP\); int16 is 2-byte value`
MOVB y+2(FP), R3 // want `invalid MOVB of y\+2\(FP\); uint16 is 2-byte value`
MOVHZ x+0(FP), R3
MOVH y+2(FP), R4
MOVWZ x+0(FP), R3 // want `invalid MOVWZ of x\+0\(FP\); int16 is 2-byte value`
MOVW y+2(FP), R3 // want `invalid MOVW of y\+2\(FP\); uint16 is 2-byte value`
MOVD x+0(FP), R3 // want `invalid MOVD of x\+0\(FP\); int16 is 2-byte value`
MOVD y+2(FP), R3 // want `invalid MOVD of y\+2\(FP\); uint16 is 2-byte value`
MOVHZ x+2(FP), R3 // want `invalid offset x\+2\(FP\); expected x\+0\(FP\)`
MOVH y+0(FP), R3 // want `invalid offset y\+0\(FP\); expected y\+2\(FP\)`
RET
TEXT ·arg4(SB),0,$0-2 // want `arg4: wrong argument size 2; expected \$\.\.\.-8`
MOVB x+0(FP), R3 // want `invalid MOVB of x\+0\(FP\); int32 is 4-byte value`
MOVB y+4(FP), R4 // want `invalid MOVB of y\+4\(FP\); uint32 is 4-byte value`
MOVH x+0(FP), R3 // want `invalid MOVH of x\+0\(FP\); int32 is 4-byte value`
MOVH y+4(FP), R3 // want `invalid MOVH of y\+4\(FP\); uint32 is 4-byte value`
MOVW x+0(FP), R3
MOVW y+4(FP), R3
MOVD x+0(FP), R3 // want `invalid MOVD of x\+0\(FP\); int32 is 4-byte value`
MOVD y+4(FP), R3 // want `invalid MOVD of y\+4\(FP\); uint32 is 4-byte value`
MOVW x+4(FP), R3 // want `invalid offset x\+4\(FP\); expected x\+0\(FP\)`
MOVW y+2(FP), R3 // want `invalid offset y\+2\(FP\); expected y\+4\(FP\)`
RET
TEXT ·arg8(SB),7,$0-2 // want `wrong argument size 2; expected \$\.\.\.-16`
MOVB x+0(FP), R3 // want `invalid MOVB of x\+0\(FP\); int64 is 8-byte value`
MOVB y+8(FP), R4 // want `invalid MOVB of y\+8\(FP\); uint64 is 8-byte value`
MOVH x+0(FP), R3 // want `invalid MOVH of x\+0\(FP\); int64 is 8-byte value`
MOVH y+8(FP), R3 // want `invalid MOVH of y\+8\(FP\); uint64 is 8-byte value`
MOVW x+0(FP), R3 // want `invalid MOVW of x\+0\(FP\); int64 is 8-byte value`
MOVW y+8(FP), R3 // want `invalid MOVW of y\+8\(FP\); uint64 is 8-byte value`
MOVD x+0(FP), R3
MOVD y+8(FP), R3
MOVD x+8(FP), R3 // want `invalid offset x\+8\(FP\); expected x\+0\(FP\)`
MOVD y+2(FP), R3 // want `invalid offset y\+2\(FP\); expected y\+8\(FP\)`
RET
TEXT ·argint(SB),0,$0-2 // want `wrong argument size 2; expected \$\.\.\.-16`
MOVB x+0(FP), R3 // want `invalid MOVB of x\+0\(FP\); int is 8-byte value`
MOVB y+8(FP), R4 // want `invalid MOVB of y\+8\(FP\); uint is 8-byte value`
MOVH x+0(FP), R3 // want `invalid MOVH of x\+0\(FP\); int is 8-byte value`
MOVH y+8(FP), R3 // want `invalid MOVH of y\+8\(FP\); uint is 8-byte value`
MOVW x+0(FP), R3 // want `invalid MOVW of x\+0\(FP\); int is 8-byte value`
MOVW y+8(FP), R3 // want `invalid MOVW of y\+8\(FP\); uint is 8-byte value`
MOVD x+0(FP), R3
MOVD y+8(FP), R3
MOVD x+8(FP), R3 // want `invalid offset x\+8\(FP\); expected x\+0\(FP\)`
MOVD y+2(FP), R3 // want `invalid offset y\+2\(FP\); expected y\+8\(FP\)`
RET
TEXT ·argptr(SB),7,$0-2 // want `wrong argument size 2; expected \$\.\.\.-40`
MOVB x+0(FP), R3 // want `invalid MOVB of x\+0\(FP\); \*byte is 8-byte value`
MOVB y+8(FP), R4 // want `invalid MOVB of y\+8\(FP\); \*byte is 8-byte value`
MOVH x+0(FP), R3 // want `invalid MOVH of x\+0\(FP\); \*byte is 8-byte value`
MOVH y+8(FP), R3 // want `invalid MOVH of y\+8\(FP\); \*byte is 8-byte value`
MOVW x+0(FP), R3 // want `invalid MOVW of x\+0\(FP\); \*byte is 8-byte value`
MOVW y+8(FP), R3 // want `invalid MOVW of y\+8\(FP\); \*byte is 8-byte value`
MOVD x+0(FP), R3
MOVD y+8(FP), R3
MOVD x+8(FP), R3 // want `invalid offset x\+8\(FP\); expected x\+0\(FP\)`
MOVD y+2(FP), R3 // want `invalid offset y\+2\(FP\); expected y\+8\(FP\)`
MOVW c+16(FP), R3 // want `invalid MOVW of c\+16\(FP\); chan int is 8-byte value`
MOVW m+24(FP), R3 // want `invalid MOVW of m\+24\(FP\); map\[int\]int is 8-byte value`
MOVW f+32(FP), R3 // want `invalid MOVW of f\+32\(FP\); func\(\) is 8-byte value`
RET
TEXT ·argstring(SB),0,$32 // want `wrong argument size 0; expected \$\.\.\.-32`
MOVH x+0(FP), R3 // want `invalid MOVH of x\+0\(FP\); string base is 8-byte value`
MOVW x+0(FP), R3 // want `invalid MOVW of x\+0\(FP\); string base is 8-byte value`
MOVD x+0(FP), R3
MOVH x_base+0(FP), R3 // want `invalid MOVH of x_base\+0\(FP\); string base is 8-byte value`
MOVW x_base+0(FP), R3 // want `invalid MOVW of x_base\+0\(FP\); string base is 8-byte value`
MOVD x_base+0(FP), R3
MOVH x_len+0(FP), R3 // want `invalid offset x_len\+0\(FP\); expected x_len\+8\(FP\)`
MOVW x_len+0(FP), R3 // want `invalid offset x_len\+0\(FP\); expected x_len\+8\(FP\)`
MOVD x_len+0(FP), R3 // want `invalid offset x_len\+0\(FP\); expected x_len\+8\(FP\)`
MOVH x_len+8(FP), R3 // want `invalid MOVH of x_len\+8\(FP\); string len is 8-byte value`
MOVW x_len+8(FP), R3 // want `invalid MOVW of x_len\+8\(FP\); string len is 8-byte value`
MOVD x_len+8(FP), R3
MOVD y+0(FP), R3 // want `invalid offset y\+0\(FP\); expected y\+16\(FP\)`
MOVD y_len+8(FP), R3 // want `invalid offset y_len\+8\(FP\); expected y_len\+24\(FP\)`
RET
TEXT ·argslice(SB),0,$48 // want `wrong argument size 0; expected \$\.\.\.-48`
MOVH x+0(FP), R3 // want `invalid MOVH of x\+0\(FP\); slice base is 8-byte value`
MOVW x+0(FP), R3 // want `invalid MOVW of x\+0\(FP\); slice base is 8-byte value`
MOVD x+0(FP), R3
MOVH x_base+0(FP), R3 // want `invalid MOVH of x_base\+0\(FP\); slice base is 8-byte value`
MOVW x_base+0(FP), R3 // want `invalid MOVW of x_base\+0\(FP\); slice base is 8-byte value`
MOVD x_base+0(FP), R3
MOVH x_len+0(FP), R3 // want `invalid offset x_len\+0\(FP\); expected x_len\+8\(FP\)`
MOVW x_len+0(FP), R3 // want `invalid offset x_len\+0\(FP\); expected x_len\+8\(FP\)`
MOVD x_len+0(FP), R3 // want `invalid offset x_len\+0\(FP\); expected x_len\+8\(FP\)`
MOVH x_len+8(FP), R3 // want `invalid MOVH of x_len\+8\(FP\); slice len is 8-byte value`
MOVW x_len+8(FP), R3 // want `invalid MOVW of x_len\+8\(FP\); slice len is 8-byte value`
MOVD x_len+8(FP), R3
MOVH x_cap+0(FP), R3 // want `invalid offset x_cap\+0\(FP\); expected x_cap\+16\(FP\)`
MOVW x_cap+0(FP), R3 // want `invalid offset x_cap\+0\(FP\); expected x_cap\+16\(FP\)`
MOVD x_cap+0(FP), R3 // want `invalid offset x_cap\+0\(FP\); expected x_cap\+16\(FP\)`
MOVH x_cap+16(FP), R3 // want `invalid MOVH of x_cap\+16\(FP\); slice cap is 8-byte value`
MOVW x_cap+16(FP), R3 // want `invalid MOVW of x_cap\+16\(FP\); slice cap is 8-byte value`
MOVD x_cap+16(FP), R3
MOVD y+0(FP), R3 // want `invalid offset y\+0\(FP\); expected y\+24\(FP\)`
MOVD y_len+8(FP), R3 // want `invalid offset y_len\+8\(FP\); expected y_len\+32\(FP\)`
MOVD y_cap+16(FP), R3 // want `invalid offset y_cap\+16\(FP\); expected y_cap\+40\(FP\)`
RET
TEXT ·argiface(SB),0,$0-32
MOVH x+0(FP), R3 // want `invalid MOVH of x\+0\(FP\); interface type is 8-byte value`
MOVW x+0(FP), R3 // want `invalid MOVW of x\+0\(FP\); interface type is 8-byte value`
MOVD x+0(FP), R3
MOVH x_type+0(FP), R3 // want `invalid MOVH of x_type\+0\(FP\); interface type is 8-byte value`
MOVW x_type+0(FP), R3 // want `invalid MOVW of x_type\+0\(FP\); interface type is 8-byte value`
MOVD x_type+0(FP), R3
MOVD x_itable+0(FP), R3 // want `unknown variable x_itable; offset 0 is x_type\+0\(FP\)`
MOVD x_itable+1(FP), R3 // want `unknown variable x_itable; offset 1 is x_type\+0\(FP\)`
MOVH x_data+0(FP), R3 // want `invalid offset x_data\+0\(FP\); expected x_data\+8\(FP\)`
MOVW x_data+0(FP), R3 // want `invalid offset x_data\+0\(FP\); expected x_data\+8\(FP\)`
MOVD x_data+0(FP), R3 // want `invalid offset x_data\+0\(FP\); expected x_data\+8\(FP\)`
MOVH x_data+8(FP), R3 // want `invalid MOVH of x_data\+8\(FP\); interface data is 8-byte value`
MOVW x_data+8(FP), R3 // want `invalid MOVW of x_data\+8\(FP\); interface data is 8-byte value`
MOVD x_data+8(FP), R3
MOVH y+16(FP), R3 // want `invalid MOVH of y\+16\(FP\); interface itable is 8-byte value`
MOVW y+16(FP), R3 // want `invalid MOVW of y\+16\(FP\); interface itable is 8-byte value`
MOVD y+16(FP), R3
MOVH y_itable+16(FP), R3 // want `invalid MOVH of y_itable\+16\(FP\); interface itable is 8-byte value`
MOVW y_itable+16(FP), R3 // want `invalid MOVW of y_itable\+16\(FP\); interface itable is 8-byte value`
MOVD y_itable+16(FP), R3
MOVD y_type+16(FP), R3 // want `unknown variable y_type; offset 16 is y_itable\+16\(FP\)`
MOVH y_data+16(FP), R3 // want `invalid offset y_data\+16\(FP\); expected y_data\+24\(FP\)`
MOVW y_data+16(FP), R3 // want `invalid offset y_data\+16\(FP\); expected y_data\+24\(FP\)`
MOVD y_data+16(FP), R3 // want `invalid offset y_data\+16\(FP\); expected y_data\+24\(FP\)`
MOVH y_data+24(FP), R3 // want `invalid MOVH of y_data\+24\(FP\); interface data is 8-byte value`
MOVW y_data+24(FP), R3 // want `invalid MOVW of y_data\+24\(FP\); interface data is 8-byte value`
MOVD y_data+24(FP), R3
RET
TEXT ·returnint(SB),0,$0-8
MOVB R3, ret+0(FP) // want `invalid MOVB of ret\+0\(FP\); int is 8-byte value`
MOVH R3, ret+0(FP) // want `invalid MOVH of ret\+0\(FP\); int is 8-byte value`
MOVW R3, ret+0(FP) // want `invalid MOVW of ret\+0\(FP\); int is 8-byte value`
MOVD R3, ret+0(FP)
MOVD R3, ret+1(FP) // want `invalid offset ret\+1\(FP\); expected ret\+0\(FP\)`
MOVD R3, r+0(FP) // want `unknown variable r; offset 0 is ret\+0\(FP\)`
RET
TEXT ·returnbyte(SB),0,$0-9
MOVD x+0(FP), R3
MOVB R3, ret+8(FP)
MOVH R3, ret+8(FP) // want `invalid MOVH of ret\+8\(FP\); byte is 1-byte value`
MOVW R3, ret+8(FP) // want `invalid MOVW of ret\+8\(FP\); byte is 1-byte value`
MOVD R3, ret+8(FP) // want `invalid MOVD of ret\+8\(FP\); byte is 1-byte value`
MOVB R3, ret+7(FP) // want `invalid offset ret\+7\(FP\); expected ret\+8\(FP\)`
RET
TEXT ·returnnamed(SB),0,$0-41
MOVB x+0(FP), R3
MOVD R3, r1+8(FP)
MOVH R3, r2+16(FP)
MOVD R3, r3+24(FP)
MOVD R3, r3_base+24(FP)
MOVD R3, r3_len+32(FP)
MOVB R3, r4+40(FP)
MOVW R3, r1+8(FP) // want `invalid MOVW of r1\+8\(FP\); int is 8-byte value`
RET
TEXT ·returnintmissing(SB),0,$0-8
RET // want `RET without writing to 8-byte ret\+0\(FP\)`
// writing to result in ABIInternal function
TEXT ·returnABIInternal(SB), NOSPLIT, $8
MOVD $123, R3
RET
TEXT ·returnmissingABIInternal(SB), NOSPLIT, $8
MOVD $123, R10
RET // want `RET without writing to result register`
// issue 69352
TEXT ·returnsyscallABIInternal(SB), NOSPLIT, $0
MOVD $123, R10
SYSCALL
RET
================================================
FILE: go/analysis/passes/asmdecl/testdata/src/a/asm8.s
================================================
// Copyright 2016 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build mipsle
TEXT ·arg1(SB),0,$0-2
MOVB x+0(FP), R1
MOVBU y+1(FP), R2
MOVH x+0(FP), R1 // want `\[mipsle\] arg1: invalid MOVH of x\+0\(FP\); int8 is 1-byte value`
MOVHU y+1(FP), R1 // want `invalid MOVHU of y\+1\(FP\); uint8 is 1-byte value`
MOVW x+0(FP), R1 // want `invalid MOVW of x\+0\(FP\); int8 is 1-byte value`
MOVWU y+1(FP), R1 // want `invalid MOVWU of y\+1\(FP\); uint8 is 1-byte value`
MOVW y+1(FP), R1 // want `invalid MOVW of y\+1\(FP\); uint8 is 1-byte value`
MOVB x+1(FP), R1 // want `invalid offset x\+1\(FP\); expected x\+0\(FP\)`
MOVBU y+2(FP), R1 // want `invalid offset y\+2\(FP\); expected y\+1\(FP\)`
MOVB 8(R29), R1 // want `8\(R29\) should be x\+0\(FP\)`
MOVB 9(R29), R1 // want `9\(R29\) should be y\+1\(FP\)`
MOVB 10(R29), R1 // want `use of 10\(R29\) points beyond argument frame`
RET
TEXT ·arg2(SB),0,$0-4
MOVBU x+0(FP), R1 // want `arg2: invalid MOVBU of x\+0\(FP\); int16 is 2-byte value`
MOVB y+2(FP), R1 // want `invalid MOVB of y\+2\(FP\); uint16 is 2-byte value`
MOVHU x+0(FP), R1
MOVH y+2(FP), R2
MOVWU x+0(FP), R1 // want `invalid MOVWU of x\+0\(FP\); int16 is 2-byte value`
MOVW y+2(FP), R1 // want `invalid MOVW of y\+2\(FP\); uint16 is 2-byte value`
MOVHU x+2(FP), R1 // want `invalid offset x\+2\(FP\); expected x\+0\(FP\)`
MOVH y+0(FP), R1 // want `invalid offset y\+0\(FP\); expected y\+2\(FP\)`
RET
TEXT ·arg4(SB),0,$0-2 // want `arg4: wrong argument size 2; expected \$\.\.\.-8`
MOVB x+0(FP), R1 // want `invalid MOVB of x\+0\(FP\); int32 is 4-byte value`
MOVB y+4(FP), R2 // want `invalid MOVB of y\+4\(FP\); uint32 is 4-byte value`
MOVH x+0(FP), R1 // want `invalid MOVH of x\+0\(FP\); int32 is 4-byte value`
MOVH y+4(FP), R1 // want `invalid MOVH of y\+4\(FP\); uint32 is 4-byte value`
MOVW x+0(FP), R1
MOVW y+4(FP), R1
MOVW x+4(FP), R1 // want `invalid offset x\+4\(FP\); expected x\+0\(FP\)`
MOVW y+2(FP), R1 // want `invalid offset y\+2\(FP\); expected y\+4\(FP\)`
RET
TEXT ·arg8(SB),7,$0-2 // want `wrong argument size 2; expected \$\.\.\.-16`
MOVB x+0(FP), R1 // want `invalid MOVB of x\+0\(FP\); int64 is 8-byte value`
MOVB y+8(FP), R2 // want `invalid MOVB of y\+8\(FP\); uint64 is 8-byte value`
MOVH x+0(FP), R1 // want `invalid MOVH of x\+0\(FP\); int64 is 8-byte value`
MOVH y+8(FP), R1 // want `invalid MOVH of y\+8\(FP\); uint64 is 8-byte value`
MOVW x+0(FP), R1 // want `invalid MOVW of x\+0\(FP\); int64 is 8-byte value containing x_lo\+0\(FP\) and x_hi\+4\(FP\)`
MOVW $x+0(FP), R1 // ok
MOVW x_lo+0(FP), R1
MOVW x_hi+4(FP), R1
MOVW y+8(FP), R1 // want `invalid MOVW of y\+8\(FP\); uint64 is 8-byte value containing y_lo\+8\(FP\) and y_hi\+12\(FP\)`
MOVW y_lo+8(FP), R1
MOVW y_hi+12(FP), R1
RET
TEXT ·argint(SB),0,$0-2 // want `wrong argument size 2; expected \$\.\.\.-8`
MOVB x+0(FP), R1 // want `invalid MOVB of x\+0\(FP\); int is 4-byte value`
MOVB y+4(FP), R2 // want `invalid MOVB of y\+4\(FP\); uint is 4-byte value`
MOVH x+0(FP), R1 // want `invalid MOVH of x\+0\(FP\); int is 4-byte value`
MOVH y+4(FP), R1 // want `invalid MOVH of y\+4\(FP\); uint is 4-byte value`
MOVW x+0(FP), R1
MOVW y+4(FP), R1
MOVW x+4(FP), R1 // want `invalid offset x\+4\(FP\); expected x\+0\(FP\)`
MOVW y+2(FP), R1 // want `invalid offset y\+2\(FP\); expected y\+4\(FP\)`
RET
TEXT ·argptr(SB),7,$0-2 // want `wrong argument size 2; expected \$\.\.\.-20`
MOVB x+0(FP), R1 // want `invalid MOVB of x\+0\(FP\); \*byte is 4-byte value`
MOVB y+4(FP), R2 // want `invalid MOVB of y\+4\(FP\); \*byte is 4-byte value`
MOVH x+0(FP), R1 // want `invalid MOVH of x\+0\(FP\); \*byte is 4-byte value`
MOVH y+4(FP), R1 // want `invalid MOVH of y\+4\(FP\); \*byte is 4-byte value`
MOVW x+0(FP), R1
MOVW y+4(FP), R1
MOVW x+4(FP), R1 // want `invalid offset x\+4\(FP\); expected x\+0\(FP\)`
MOVW y+2(FP), R1 // want `invalid offset y\+2\(FP\); expected y\+4\(FP\)`
MOVH c+8(FP), R1 // want `invalid MOVH of c\+8\(FP\); chan int is 4-byte value`
MOVH m+12(FP), R1 // want `invalid MOVH of m\+12\(FP\); map\[int\]int is 4-byte value`
MOVH f+16(FP), R1 // want `invalid MOVH of f\+16\(FP\); func\(\) is 4-byte value`
RET
TEXT ·argstring(SB),0,$16 // want `wrong argument size 0; expected \$\.\.\.-16`
MOVH x+0(FP), R1 // want `invalid MOVH of x\+0\(FP\); string base is 4-byte value`
MOVW x+0(FP), R1
MOVH x_base+0(FP), R1 // want `invalid MOVH of x_base\+0\(FP\); string base is 4-byte value`
MOVW x_base+0(FP), R1
MOVH x_len+0(FP), R1 // want `invalid offset x_len\+0\(FP\); expected x_len\+4\(FP\)`
MOVW x_len+0(FP), R1 // want `invalid offset x_len\+0\(FP\); expected x_len\+4\(FP\)`
MOVH x_len+4(FP), R1 // want `invalid MOVH of x_len\+4\(FP\); string len is 4-byte value`
MOVW x_len+4(FP), R1
MOVW y+0(FP), R1 // want `invalid offset y\+0\(FP\); expected y\+8\(FP\)`
MOVW y_len+4(FP), R1 // want `invalid offset y_len\+4\(FP\); expected y_len\+12\(FP\)`
RET
TEXT ·argslice(SB),0,$24 // want `wrong argument size 0; expected \$\.\.\.-24`
MOVH x+0(FP), R1 // want `invalid MOVH of x\+0\(FP\); slice base is 4-byte value`
MOVW x+0(FP), R1
MOVH x_base+0(FP), R1 // want `invalid MOVH of x_base\+0\(FP\); slice base is 4-byte value`
MOVW x_base+0(FP), R1
MOVH x_len+0(FP), R1 // want `invalid offset x_len\+0\(FP\); expected x_len\+4\(FP\)`
MOVW x_len+0(FP), R1 // want `invalid offset x_len\+0\(FP\); expected x_len\+4\(FP\)`
MOVH x_len+4(FP), R1 // want `invalid MOVH of x_len\+4\(FP\); slice len is 4-byte value`
MOVW x_len+4(FP), R1
MOVH x_cap+0(FP), R1 // want `invalid offset x_cap\+0\(FP\); expected x_cap\+8\(FP\)`
MOVW x_cap+0(FP), R1 // want `invalid offset x_cap\+0\(FP\); expected x_cap\+8\(FP\)`
MOVH x_cap+8(FP), R1 // want `invalid MOVH of x_cap\+8\(FP\); slice cap is 4-byte value`
MOVW x_cap+8(FP), R1
MOVW y+0(FP), R1 // want `invalid offset y\+0\(FP\); expected y\+12\(FP\)`
MOVW y_len+4(FP), R1 // want `invalid offset y_len\+4\(FP\); expected y_len\+16\(FP\)`
MOVW y_cap+8(FP), R1 // want `invalid offset y_cap\+8\(FP\); expected y_cap\+20\(FP\)`
RET
TEXT ·argiface(SB),0,$0-16
MOVH x+0(FP), R1 // want `invalid MOVH of x\+0\(FP\); interface type is 4-byte value`
MOVW x+0(FP), R1
MOVH x_type+0(FP), R1 // want `invalid MOVH of x_type\+0\(FP\); interface type is 4-byte value`
MOVW x_type+0(FP), R1
MOVQ x_itable+0(FP), R1 // want `unknown variable x_itable; offset 0 is x_type\+0\(FP\)`
MOVQ x_itable+1(FP), R1 // want `unknown variable x_itable; offset 1 is x_type\+0\(FP\)`
MOVH x_data+0(FP), R1 // want `invalid offset x_data\+0\(FP\); expected x_data\+4\(FP\)`
MOVW x_data+0(FP), R1 // want `invalid offset x_data\+0\(FP\); expected x_data\+4\(FP\)`
MOVQ x_data+0(FP), R1 // want `invalid offset x_data\+0\(FP\); expected x_data\+4\(FP\)`
MOVH x_data+4(FP), R1 // want `invalid MOVH of x_data\+4\(FP\); interface data is 4-byte value`
MOVW x_data+4(FP), R1
MOVH y+8(FP), R1 // want `invalid MOVH of y\+8\(FP\); interface itable is 4-byte value`
MOVW y+8(FP), R1
MOVH y_itable+8(FP), R1 // want `invalid MOVH of y_itable\+8\(FP\); interface itable is 4-byte value`
MOVW y_itable+8(FP), R1
MOVW y_type+8(FP), AX // want `unknown variable y_type; offset 8 is y_itable\+8\(FP\)`
MOVH y_data+8(FP), AX // want `invalid offset y_data\+8\(FP\); expected y_data\+12\(FP\)`
MOVW y_data+8(FP), AX // want `invalid offset y_data\+8\(FP\); expected y_data\+12\(FP\)`
MOVH y_data+12(FP), AX // want `invalid MOVH of y_data\+12\(FP\); interface data is 4-byte value`
MOVW y_data+12(FP), AX
RET
TEXT ·returnbyte(SB),0,$0-5
MOVW x+0(FP), R1
MOVB R1, ret+4(FP)
MOVH R1, ret+4(FP) // want `invalid MOVH of ret\+4\(FP\); byte is 1-byte value`
MOVW R1, ret+4(FP) // want `invalid MOVW of ret\+4\(FP\); byte is 1-byte value`
MOVB R1, ret+3(FP) // want `invalid offset ret\+3\(FP\); expected ret\+4\(FP\)`
RET
TEXT ·returnbyte(SB),0,$0-5
MOVW x+0(FP), R1
MOVB R1, ret+4(FP)
MOVH R1, ret+4(FP) // want `invalid MOVH of ret\+4\(FP\); byte is 1-byte value`
MOVW R1, ret+4(FP) // want `invalid MOVW of ret\+4\(FP\); byte is 1-byte value`
MOVB R1, ret+3(FP) // want `invalid offset ret\+3\(FP\); expected ret\+4\(FP\)`
RET
TEXT ·returnnamed(SB),0,$0-21
MOVB x+0(FP), AX
MOVW R1, r1+4(FP)
MOVH R1, r2+8(FP)
MOVW R1, r3+12(FP)
MOVW R1, r3_base+12(FP)
MOVW R1, r3_len+16(FP)
MOVB R1, r4+20(FP)
MOVB R1, r1+4(FP) // want `invalid MOVB of r1\+4\(FP\); int is 4-byte value`
RET
TEXT ·returnintmissing(SB),0,$0-4
RET // want `RET without writing to 4-byte ret\+0\(FP\)`
================================================
FILE: go/analysis/passes/asmdecl/testdata/src/a/asm9.s
================================================
// Copyright 2019 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build wasm
TEXT ·returnint(SB),NOSPLIT|NOFRAME,$0-8
MOVD 24(SP), R1 // ok to access beyond stack frame with NOFRAME
CallImport // interpreted as writing result
RET
TEXT ·returnbyte(SB),$0-9
RET // want `RET without writing`
================================================
FILE: go/analysis/passes/assign/assign.go
================================================
// Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package assign
// TODO(adonovan): check also for assignments to struct fields inside
// methods that are on T instead of *T.
import (
_ "embed"
"go/ast"
"go/token"
"go/types"
"reflect"
"strings"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/ast/inspector"
"golang.org/x/tools/internal/analysis/analyzerutil"
"golang.org/x/tools/internal/astutil"
"golang.org/x/tools/internal/refactor"
"golang.org/x/tools/internal/typesinternal"
)
//go:embed doc.go
var doc string
var Analyzer = &analysis.Analyzer{
Name: "assign",
Doc: analyzerutil.MustExtractDoc(doc, "assign"),
URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/assign",
Requires: []*analysis.Analyzer{inspect.Analyzer},
Run: run,
}
func run(pass *analysis.Pass) (any, error) {
var (
inspect = pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
info = pass.TypesInfo
)
for curAssign := range inspect.Root().Preorder((*ast.AssignStmt)(nil)) {
stmt := curAssign.Node().(*ast.AssignStmt)
if stmt.Tok != token.ASSIGN {
continue // ignore :=
}
if len(stmt.Lhs) != len(stmt.Rhs) {
// If LHS and RHS have different cardinality, they can't be the same.
continue
}
// Delete redundant LHS, RHS pairs, taking care
// to include intervening commas.
var (
exprs []string // expressions appearing on both sides (x = x)
edits []analysis.TextEdit
runStartLHS, runStartRHS token.Pos // non-zero => within a run
)
for i, lhs := range stmt.Lhs {
rhs := stmt.Rhs[i]
isSelfAssign := false
var le string
if typesinternal.NoEffects(info, lhs) &&
typesinternal.NoEffects(info, rhs) &&
!isMapIndex(info, lhs) &&
reflect.TypeOf(lhs) == reflect.TypeOf(rhs) { // short-circuit the heavy-weight gofmt check
le = astutil.Format(pass.Fset, lhs)
re := astutil.Format(pass.Fset, rhs)
if le == re {
isSelfAssign = true
}
}
if isSelfAssign {
exprs = append(exprs, le)
if !runStartLHS.IsValid() {
// Start of a new run of self-assignments.
if i > 0 {
runStartLHS = stmt.Lhs[i-1].End()
runStartRHS = stmt.Rhs[i-1].End()
} else {
runStartLHS = lhs.Pos()
runStartRHS = rhs.Pos()
}
}
} else if runStartLHS.IsValid() {
// End of a run of self-assignments.
endLHS, endRHS := stmt.Lhs[i-1].End(), stmt.Rhs[i-1].End()
if runStartLHS == stmt.Lhs[0].Pos() {
endLHS, endRHS = lhs.Pos(), rhs.Pos()
}
edits = append(edits,
analysis.TextEdit{Pos: runStartLHS, End: endLHS},
analysis.TextEdit{Pos: runStartRHS, End: endRHS},
)
runStartLHS, runStartRHS = 0, 0
}
}
// If a run of self-assignments continues to the end of the statement, close it.
if runStartLHS.IsValid() {
last := len(stmt.Lhs) - 1
edits = append(edits,
analysis.TextEdit{Pos: runStartLHS, End: stmt.Lhs[last].End()},
analysis.TextEdit{Pos: runStartRHS, End: stmt.Rhs[last].End()},
)
}
if len(exprs) == 0 {
continue
}
if len(exprs) == len(stmt.Lhs) {
// If every part of the statement is a self-assignment,
// remove the whole statement.
tokFile := pass.Fset.File(stmt.Pos())
edits = refactor.DeleteStmt(tokFile, curAssign)
}
pass.Report(analysis.Diagnostic{
Pos: stmt.Pos(),
Message: "self-assignment of " + strings.Join(exprs, ", "),
SuggestedFixes: []analysis.SuggestedFix{{
Message: "Remove self-assignment",
TextEdits: edits,
}},
})
}
return nil, nil
}
// isMapIndex returns true if e is a map index expression.
func isMapIndex(info *types.Info, e ast.Expr) bool {
if idx, ok := ast.Unparen(e).(*ast.IndexExpr); ok {
if typ := info.Types[idx.X].Type; typ != nil {
_, ok := typ.Underlying().(*types.Map)
return ok
}
}
return false
}
================================================
FILE: go/analysis/passes/assign/assign_test.go
================================================
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package assign_test
import (
"testing"
"golang.org/x/tools/go/analysis/analysistest"
"golang.org/x/tools/go/analysis/passes/assign"
)
func Test(t *testing.T) {
testdata := analysistest.TestData()
analysistest.RunWithSuggestedFixes(t, testdata, assign.Analyzer, "a", "typeparams")
}
================================================
FILE: go/analysis/passes/assign/doc.go
================================================
// Copyright 2023 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package assign defines an Analyzer that detects useless assignments.
//
// # Analyzer assign
//
// assign: check for useless assignments
//
// This checker reports assignments of the form x = x or a[i] = a[i].
// These are almost always useless, and even when they aren't they are
// usually a mistake.
package assign
================================================
FILE: go/analysis/passes/assign/testdata/src/a/a.go
================================================
// Copyright 2020 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// This file contains tests for the useless-assignment checker.
package testdata
import "math/rand"
type ST struct {
x int
l []int
}
func (s *ST) SetX(x int, ch chan int) {
// Accidental self-assignment; it should be "s.x = x"
x = x // want "self-assignment of x"
// Another mistake
s.x = s.x // want "self-assignment of s.x"
s.l[0] = s.l[0] // want "self-assignment of s.l.0."
// Report self-assignment to x but preserve the actual assignment to s.x
x, s.x = x, 1 // want "self-assignment of x"
s.x, x = 1, x // want "self-assignment of x"
// Delete multiple self-assignment
x, s.x = x, s.x // want "self-assignment of x, s.x"
s.l[0], x, s.x = 1, x, s.x // want "self-assignment of x, s.x"
x, s.l[0], s.x = x, 1, s.x // want "self-assignment of x, s.x"
x, s.x, s.l[0] = x, s.x, 1 // want "self-assignment of x, s.x"
s.l[0], x, s.x, s.l[1] = 1, x, s.x, 1 // want "self-assignment of x, s.x"
x, s.l[0], s.l[1], s.x = x, 1, 1, s.x // want "self-assignment of x, s.x"
// Bail on any potential side effects to avoid false positives
s.l[num()] = s.l[num()]
rng := rand.New(rand.NewSource(0))
s.l[rng.Intn(len(s.l))] = s.l[rng.Intn(len(s.l))]
s.l[<-ch] = s.l[<-ch]
}
func num() int { return 2 }
func Index() {
s := []int{1}
s[0] = s[0] // want "self-assignment"
var a [5]int
a[0] = a[0] // want "self-assignment"
pa := &[2]int{1, 2}
pa[1] = pa[1] // want "self-assignment"
var pss *struct { // report self assignment despite nil dereference
s []int
}
pss.s[0] = pss.s[0] // want "self-assignment"
m := map[int]string{1: "a"}
m[0] = m[0] // bail on map self-assignments due to side effects
m[1] = m[1] // not modeling what elements must be in the map
(m[2]) = (m[2]) // even with parens
type Map map[string]bool
named := make(Map)
named["s"] = named["s"] // even on named maps.
var psm *struct {
m map[string]int
}
psm.m["key"] = psm.m["key"] // handles dereferences
}
================================================
FILE: go/analysis/passes/assign/testdata/src/a/a.go.golden
================================================
// Copyright 2020 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// This file contains tests for the useless-assignment checker.
package testdata
import "math/rand"
type ST struct {
x int
l []int
}
func (s *ST) SetX(x int, ch chan int) {
// Accidental self-assignment; it should be "s.x = x"
// Another mistake
// Report self-assignment to x but preserve the actual assignment to s.x
s.x = 1 // want "self-assignment of x"
s.x = 1 // want "self-assignment of x"
// Delete multiple self-assignment
s.l[0] = 1 // want "self-assignment of x, s.x"
s.l[0] = 1 // want "self-assignment of x, s.x"
s.l[0] = 1 // want "self-assignment of x, s.x"
s.l[0], s.l[1] = 1, 1 // want "self-assignment of x, s.x"
s.l[0], s.l[1] = 1, 1 // want "self-assignment of x, s.x"
// Bail on any potential side effects to avoid false positives
s.l[num()] = s.l[num()]
rng := rand.New(rand.NewSource(0))
s.l[rng.Intn(len(s.l))] = s.l[rng.Intn(len(s.l))]
s.l[<-ch] = s.l[<-ch]
}
func num() int { return 2 }
func Index() {
s := []int{1}
var a [5]int
pa := &[2]int{1, 2}
var pss *struct { // report self assignment despite nil dereference
s []int
}
m := map[int]string{1: "a"}
m[0] = m[0] // bail on map self-assignments due to side effects
m[1] = m[1] // not modeling what elements must be in the map
(m[2]) = (m[2]) // even with parens
type Map map[string]bool
named := make(Map)
named["s"] = named["s"] // even on named maps.
var psm *struct {
m map[string]int
}
psm.m["key"] = psm.m["key"] // handles dereferences
}
================================================
FILE: go/analysis/passes/assign/testdata/src/typeparams/typeparams.go
================================================
// Copyright 2020 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// This file contains tests for the useless-assignment checker.
package testdata
import "math/rand"
type ST[T interface{ ~int }] struct {
x T
l []T
}
func (s *ST[T]) SetX(x T, ch chan T) {
// Accidental self-assignment; it should be "s.x = x"
x = x // want "self-assignment of x"
// Another mistake
s.x = s.x // want "self-assignment of s.x"
s.l[0] = s.l[0] // want "self-assignment of s.l.0."
// Bail on any potential side effects to avoid false positives
s.l[num()] = s.l[num()]
rng := rand.New(rand.NewSource(0))
s.l[rng.Intn(len(s.l))] = s.l[rng.Intn(len(s.l))]
s.l[<-ch] = s.l[<-ch]
}
func num() int { return 2 }
================================================
FILE: go/analysis/passes/assign/testdata/src/typeparams/typeparams.go.golden
================================================
// Copyright 2020 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// This file contains tests for the useless-assignment checker.
package testdata
import "math/rand"
type ST[T interface{ ~int }] struct {
x T
l []T
}
func (s *ST[T]) SetX(x T, ch chan T) {
// Accidental self-assignment; it should be "s.x = x"
// Another mistake
// Bail on any potential side effects to avoid false positives
s.l[num()] = s.l[num()]
rng := rand.New(rand.NewSource(0))
s.l[rng.Intn(len(s.l))] = s.l[rng.Intn(len(s.l))]
s.l[<-ch] = s.l[<-ch]
}
func num() int { return 2 }
================================================
FILE: go/analysis/passes/atomic/atomic.go
================================================
// Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package atomic
import (
_ "embed"
"go/ast"
"go/token"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/ast/inspector"
"golang.org/x/tools/go/types/typeutil"
"golang.org/x/tools/internal/analysis/analyzerutil"
"golang.org/x/tools/internal/astutil"
"golang.org/x/tools/internal/typesinternal"
)
//go:embed doc.go
var doc string
var Analyzer = &analysis.Analyzer{
Name: "atomic",
Doc: analyzerutil.MustExtractDoc(doc, "atomic"),
URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/atomic",
Requires: []*analysis.Analyzer{inspect.Analyzer},
RunDespiteErrors: true,
Run: run,
}
func run(pass *analysis.Pass) (any, error) {
if !typesinternal.Imports(pass.Pkg, "sync/atomic") {
return nil, nil // doesn't directly import sync/atomic
}
inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
nodeFilter := []ast.Node{
(*ast.AssignStmt)(nil),
}
inspect.Preorder(nodeFilter, func(node ast.Node) {
n := node.(*ast.AssignStmt)
if len(n.Lhs) != len(n.Rhs) {
return
}
if len(n.Lhs) == 1 && n.Tok == token.DEFINE {
return
}
for i, right := range n.Rhs {
call, ok := right.(*ast.CallExpr)
if !ok {
continue
}
obj := typeutil.Callee(pass.TypesInfo, call)
if typesinternal.IsFunctionNamed(obj, "sync/atomic", "AddInt32", "AddInt64", "AddUint32", "AddUint64", "AddUintptr") {
checkAtomicAddAssignment(pass, n.Lhs[i], call)
}
}
})
return nil, nil
}
// checkAtomicAddAssignment walks the atomic.Add* method calls checking
// for assigning the return value to the same variable being used in the
// operation
func checkAtomicAddAssignment(pass *analysis.Pass, left ast.Expr, call *ast.CallExpr) {
if len(call.Args) != 2 {
return
}
arg := call.Args[0]
broken := false
gofmt := func(e ast.Expr) string { return astutil.Format(pass.Fset, e) }
if uarg, ok := arg.(*ast.UnaryExpr); ok && uarg.Op == token.AND {
broken = gofmt(left) == gofmt(uarg.X)
} else if star, ok := left.(*ast.StarExpr); ok {
broken = gofmt(star.X) == gofmt(arg)
}
if broken {
pass.ReportRangef(left, "direct assignment to atomic value")
}
}
================================================
FILE: go/analysis/passes/atomic/atomic_test.go
================================================
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package atomic_test
import (
"testing"
"golang.org/x/tools/go/analysis/analysistest"
"golang.org/x/tools/go/analysis/passes/atomic"
)
func Test(t *testing.T) {
testdata := analysistest.TestData()
analysistest.Run(t, testdata, atomic.Analyzer, "a", "typeparams")
}
================================================
FILE: go/analysis/passes/atomic/doc.go
================================================
// Copyright 2023 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package atomic defines an Analyzer that checks for common mistakes
// using the sync/atomic package.
//
// # Analyzer atomic
//
// atomic: check for common mistakes using the sync/atomic package
//
// The atomic checker looks for assignment statements of the form:
//
// x = atomic.AddUint64(&x, 1)
//
// which are not atomic.
package atomic
================================================
FILE: go/analysis/passes/atomic/testdata/src/a/a.go
================================================
// Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// This file contains tests for the atomic checker.
package a
import (
"sync/atomic"
)
type Counter uint64
func AtomicTests() {
x := uint64(1)
x = atomic.AddUint64(&x, 1) // want "direct assignment to atomic value"
_, x = 10, atomic.AddUint64(&x, 1) // want "direct assignment to atomic value"
x, _ = atomic.AddUint64(&x, 1), 10 // want "direct assignment to atomic value"
x, _ = (atomic.AddUint64)(&x, 1), 10 // want "direct assignment to atomic value"
y := &x
*y = atomic.AddUint64(y, 1) // want "direct assignment to atomic value"
var su struct{ Counter uint64 }
su.Counter = atomic.AddUint64(&su.Counter, 1) // want "direct assignment to atomic value"
z1 := atomic.AddUint64(&su.Counter, 1)
_ = z1 // Avoid err "z declared and not used"
var sp struct{ Counter *uint64 }
*sp.Counter = atomic.AddUint64(sp.Counter, 1) // want "direct assignment to atomic value"
z2 := atomic.AddUint64(sp.Counter, 1)
_ = z2 // Avoid err "z declared and not used"
au := []uint64{10, 20}
au[0] = atomic.AddUint64(&au[0], 1) // want "direct assignment to atomic value"
au[1] = atomic.AddUint64(&au[0], 1)
ap := []*uint64{&au[0], &au[1]}
*ap[0] = atomic.AddUint64(ap[0], 1) // want "direct assignment to atomic value"
*ap[1] = atomic.AddUint64(ap[0], 1)
x = atomic.AddUint64() // Used to make vet crash; now silently ignored.
{
// A variable declaration creates a new variable in the current scope.
x := atomic.AddUint64(&x, 1)
// Re-declaration assigns a new value.
x, w := atomic.AddUint64(&x, 1), 10 // want "direct assignment to atomic value"
_ = w
}
}
type T struct{}
func (T) AddUint64(addr *uint64, delta uint64) uint64 { return 0 }
func NonAtomic() {
x := uint64(1)
var atomic T
x = atomic.AddUint64(&x, 1) // ok; not the imported pkg
}
================================================
FILE: go/analysis/passes/atomic/testdata/src/typeparams/typeparams.go
================================================
// Copyright 2021 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// This file contains tests for the atomic checker.
package a
import (
"sync/atomic"
)
type Subtractable interface {
~int64
}
func Sub[T Subtractable](addr *T, delta T) T {
// the followings result in type errors, but doesn't stop this vet check
*addr = atomic.AddInt64(addr, -delta) // want "direct assignment to atomic value"
*addr = atomic.AddUintptr(addr, delta) // want "direct assignment to atomic value"
atomic.AddInt64() // vet ignores it
return *addr
}
type _S[T Subtractable] struct {
x *T
}
func (v _S) AddInt64(_ *int64, delta int64) int64 {
*v.x = atomic.AddInt64(v.x, delta) // want "direct assignment to atomic value"
return *v.x
}
func NonAtomicInt64() {
var atomic _S[int64]
*atomic.x = atomic.AddInt64(atomic.x, 123) // ok; AddInt64 is not sync/atomic.AddInt64.
}
================================================
FILE: go/analysis/passes/atomicalign/atomicalign.go
================================================
// Copyright 2019 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package atomicalign defines an Analyzer that checks for non-64-bit-aligned
// arguments to sync/atomic functions. On non-32-bit platforms, those functions
// panic if their argument variables are not 64-bit aligned. It is therefore
// the caller's responsibility to arrange for 64-bit alignment of such variables.
// See https://golang.org/pkg/sync/atomic/#pkg-note-BUG
package atomicalign
import (
"go/ast"
"go/token"
"go/types"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/ast/inspector"
"golang.org/x/tools/go/types/typeutil"
"golang.org/x/tools/internal/typesinternal"
)
const Doc = "check for non-64-bits-aligned arguments to sync/atomic functions"
var Analyzer = &analysis.Analyzer{
Name: "atomicalign",
Doc: Doc,
URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/atomicalign",
Requires: []*analysis.Analyzer{inspect.Analyzer},
Run: run,
}
func run(pass *analysis.Pass) (any, error) {
if 8*pass.TypesSizes.Sizeof(types.Typ[types.Uintptr]) == 64 {
return nil, nil // 64-bit platform
}
if !typesinternal.Imports(pass.Pkg, "sync/atomic") {
return nil, nil // doesn't directly import sync/atomic
}
inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
nodeFilter := []ast.Node{
(*ast.CallExpr)(nil),
}
funcNames := []string{
"AddInt64", "AddUint64",
"LoadInt64", "LoadUint64",
"StoreInt64", "StoreUint64",
"SwapInt64", "SwapUint64",
"CompareAndSwapInt64", "CompareAndSwapUint64",
}
inspect.Preorder(nodeFilter, func(node ast.Node) {
call := node.(*ast.CallExpr)
obj := typeutil.Callee(pass.TypesInfo, call)
if typesinternal.IsFunctionNamed(obj, "sync/atomic", funcNames...) {
// For all the listed functions, the expression to check is always the first function argument.
check64BitAlignment(pass, obj.Name(), call.Args[0])
}
})
return nil, nil
}
func check64BitAlignment(pass *analysis.Pass, funcName string, arg ast.Expr) {
// Checks the argument is made of the address operator (&) applied to
// a struct field (as opposed to a variable as the first word of
// uint64 and int64 variables can be relied upon to be 64-bit aligned).
unary, ok := arg.(*ast.UnaryExpr)
if !ok || unary.Op != token.AND {
return
}
// Retrieve the types.Struct in order to get the offset of the
// atomically accessed field.
sel, ok := unary.X.(*ast.SelectorExpr)
if !ok {
return
}
tvar, ok := pass.TypesInfo.Selections[sel].Obj().(*types.Var)
if !ok || !tvar.IsField() {
return
}
stype, ok := pass.TypesInfo.Types[sel.X].Type.Underlying().(*types.Struct)
if !ok {
return
}
var offset int64
var fields []*types.Var
for i := 0; i < stype.NumFields(); i++ {
f := stype.Field(i)
fields = append(fields, f)
if f == tvar {
// We're done, this is the field we were looking for,
// no need to fill the fields slice further.
offset = pass.TypesSizes.Offsetsof(fields)[i]
break
}
}
if offset&7 == 0 {
return // 64-bit aligned
}
pass.ReportRangef(arg, "address of non 64-bit aligned field .%s passed to atomic.%s", tvar.Name(), funcName)
}
================================================
FILE: go/analysis/passes/atomicalign/atomicalign_test.go
================================================
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package atomicalign_test
import (
"testing"
"golang.org/x/tools/go/analysis/analysistest"
"golang.org/x/tools/go/analysis/passes/atomicalign"
)
func Test(t *testing.T) {
testdata := analysistest.TestData()
analysistest.Run(t, testdata, atomicalign.Analyzer, "a", "b")
}
================================================
FILE: go/analysis/passes/atomicalign/testdata/src/a/a.go
================================================
// Copyright 2019 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// This file contains tests for the atomic alignment checker.
//go:build arm || 386
// +build arm 386
package testdata
import (
"io"
"sync/atomic"
)
func intsAlignment() {
var s struct {
a bool
b uint8
c int8
d byte
f int16
g int16
h int64
i byte
j uint64
}
atomic.AddInt64(&s.h, 9)
atomic.AddUint64(&s.j, 0) // want "address of non 64-bit aligned field .j passed to atomic.AddUint64"
}
func floatAlignment() {
var s struct {
a float32
b int64
c float32
d float64
e uint64
}
atomic.LoadInt64(&s.b) // want "address of non 64-bit aligned field .b passed to atomic.LoadInt64"
atomic.LoadUint64(&s.e)
}
func uintptrAlignment() {
var s struct {
a uintptr
b int64
c int
d uint
e int32
f uint64
}
atomic.StoreInt64(&s.b, 0) // want "address of non 64-bit aligned field .b passed to atomic.StoreInt64"
atomic.StoreUint64(&s.f, 0)
}
func runeAlignment() {
var s struct {
a rune
b int64
_ rune
c uint64
}
atomic.SwapInt64(&s.b, 0) // want "address of non 64-bit aligned field .b passed to atomic.SwapInt64"
atomic.SwapUint64(&s.c, 0)
}
func complexAlignment() {
var s struct {
a complex64
b int64
c complex128
d uint64
}
atomic.CompareAndSwapInt64(&s.b, 0, 1)
atomic.CompareAndSwapUint64(&s.d, 0, 1)
}
// continuer ici avec les tests
func channelAlignment() {
var a struct {
a chan struct{}
b int64
c <-chan struct{}
d uint64
}
atomic.AddInt64(&a.b, 0) // want "address of non 64-bit aligned field .b passed to atomic.AddInt64"
atomic.AddUint64(&a.d, 0)
}
func arrayAlignment() {
var a struct {
a [1]uint16
b int64
_ [2]uint16
c int64
d [1]uint16
e uint64
}
atomic.LoadInt64(&a.b) // want "address of non 64-bit aligned field .b passed to atomic.LoadInt64"
atomic.LoadInt64(&a.c)
atomic.LoadUint64(&a.e) // want "address of non 64-bit aligned field .e passed to atomic.LoadUint64"
(atomic.LoadUint64)(&a.e) // want "address of non 64-bit aligned field .e passed to atomic.LoadUint64"
}
func anonymousFieldAlignment() {
var f struct {
a, b int32
c, d int64
_ bool
e, f uint64
}
atomic.StoreInt64(&f.c, 12)
atomic.StoreInt64(&f.d, 27)
atomic.StoreUint64(&f.e, 6) // want "address of non 64-bit aligned field .e passed to atomic.StoreUint64"
atomic.StoreUint64(&f.f, 79) // want "address of non 64-bit aligned field .f passed to atomic.StoreUint64"
}
type ts struct {
e int64
e2 []int
f uint64
}
func typedStructAlignment() {
var b ts
atomic.SwapInt64(&b.e, 9)
atomic.SwapUint64(&b.f, 9) // want "address of non 64-bit aligned field .f passed to atomic.SwapUint64"
}
func aliasAlignment() {
type (
mybytea uint8
mybyteb byte
mybytec = uint8
mybyted = byte
)
var e struct {
a byte
b mybytea
c mybyteb
e mybytec
f int64
g, h uint16
i uint64
}
atomic.CompareAndSwapInt64(&e.f, 0, 1) // want "address of non 64-bit aligned field .f passed to atomic.CompareAndSwapInt64"
atomic.CompareAndSwapUint64(&e.i, 1, 2)
}
func stringAlignment() {
var a struct {
a uint32
b string
c int64
}
atomic.AddInt64(&a.c, 10) // want "address of non 64-bit aligned field .c passed to atomic.AddInt64"
}
func sliceAlignment() {
var s struct {
a []int32
b int64
c uint32
d uint64
}
atomic.LoadInt64(&s.b) // want "address of non 64-bit aligned field .b passed to atomic.LoadInt64"
atomic.LoadUint64(&s.d)
}
func interfaceAlignment() {
var s struct {
a interface{}
b int64
c io.Writer
e int64
_ int32
f uint64
}
atomic.StoreInt64(&s.b, 9)
atomic.StoreInt64(&s.e, 9)
atomic.StoreUint64(&s.f, 9) // want "address of non 64-bit aligned field .f passed to atomic.StoreUint64"
}
func pointerAlignment() {
var s struct {
a, b *int
c int64
d *interface{}
e uint64
}
atomic.SwapInt64(&s.c, 9)
atomic.SwapUint64(&s.e, 9) // want "address of non 64-bit aligned field .e passed to atomic.SwapUint64"
}
// non-struct fields are already 64-bits correctly aligned per Go spec
func nonStructFields() {
var (
a *int64
b [2]uint64
c int64
)
atomic.CompareAndSwapInt64(a, 10, 11)
atomic.CompareAndSwapUint64(&b[0], 5, 23)
atomic.CompareAndSwapInt64(&c, -1, -15)
}
func embeddedStructFields() {
var s1 struct {
_ struct{ _ int32 }
a int64
_ struct{}
b uint64
_ struct{ _ [2]uint16 }
c int64
}
atomic.AddInt64(&s1.a, 9) // want "address of non 64-bit aligned field .a passed to atomic.AddInt64"
atomic.AddUint64(&s1.b, 9) // want "address of non 64-bit aligned field .b passed to atomic.AddUint64"
atomic.AddInt64(&s1.c, 9)
}
================================================
FILE: go/analysis/passes/atomicalign/testdata/src/a/stub.go
================================================
// Copyright 2019 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// This file is only here to not trigger "build constraints exclude all Go files" during tests
package testdata
================================================
FILE: go/analysis/passes/atomicalign/testdata/src/b/b.go
================================================
// Copyright 2019 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build !arm,!386
package testdata
import (
"sync/atomic"
)
func nonAffectedArchs() {
var s struct {
_ bool
a uint64
}
atomic.SwapUint64(&s.a, 9) // ok on 64-bit architectures
}
================================================
FILE: go/analysis/passes/atomicalign/testdata/src/b/stub.go
================================================
// Copyright 2019 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// This file is only here to not trigger "build constraints exclude all Go files" during tests
package testdata
================================================
FILE: go/analysis/passes/bools/bools.go
================================================
// Copyright 2014 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package bools defines an Analyzer that detects common mistakes
// involving boolean operators.
package bools
import (
"go/ast"
"go/token"
"go/types"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/ast/inspector"
"golang.org/x/tools/internal/astutil"
"golang.org/x/tools/internal/typesinternal"
)
const Doc = "check for common mistakes involving boolean operators"
var Analyzer = &analysis.Analyzer{
Name: "bools",
Doc: Doc,
URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/bools",
Requires: []*analysis.Analyzer{inspect.Analyzer},
Run: run,
}
func run(pass *analysis.Pass) (any, error) {
inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
nodeFilter := []ast.Node{
(*ast.BinaryExpr)(nil),
}
seen := make(map[*ast.BinaryExpr]bool)
inspect.Preorder(nodeFilter, func(n ast.Node) {
e := n.(*ast.BinaryExpr)
if seen[e] {
// Already processed as a subexpression of an earlier node.
return
}
var op boolOp
switch e.Op {
case token.LOR:
op = or
case token.LAND:
op = and
default:
return
}
comm := op.commutativeSets(pass.TypesInfo, e, seen)
for _, exprs := range comm {
op.checkRedundant(pass, exprs)
op.checkSuspect(pass, exprs)
}
})
return nil, nil
}
type boolOp struct {
name string
tok token.Token // token corresponding to this operator
badEq token.Token // token corresponding to the equality test that should not be used with this operator
}
var (
or = boolOp{"or", token.LOR, token.NEQ}
and = boolOp{"and", token.LAND, token.EQL}
)
// commutativeSets returns all side effect free sets of
// expressions in e that are connected by op.
// For example, given 'a || b || f() || c || d' with the or op,
// commutativeSets returns {{b, a}, {d, c}}.
// commutativeSets adds any expanded BinaryExprs to seen.
func (op boolOp) commutativeSets(info *types.Info, e *ast.BinaryExpr, seen map[*ast.BinaryExpr]bool) [][]ast.Expr {
exprs := op.split(e, seen)
// Partition the slice of expressions into commutative sets.
i := 0
var sets [][]ast.Expr
for j := 0; j <= len(exprs); j++ {
if j == len(exprs) || !typesinternal.NoEffects(info, exprs[j]) {
if i < j {
sets = append(sets, exprs[i:j])
}
i = j + 1
}
}
return sets
}
// checkRedundant checks for expressions of the form
//
// e && e
// e || e
//
// Exprs must contain only side effect free expressions.
func (op boolOp) checkRedundant(pass *analysis.Pass, exprs []ast.Expr) {
seen := make(map[string]bool)
for _, e := range exprs {
efmt := astutil.Format(pass.Fset, e)
if seen[efmt] {
pass.ReportRangef(e, "redundant %s: %s %s %s", op.name, efmt, op.tok, efmt)
} else {
seen[efmt] = true
}
}
}
// checkSuspect checks for expressions of the form
//
// x != c1 || x != c2
// x == c1 && x == c2
//
// where c1 and c2 are constant expressions.
// If c1 and c2 are the same then it's redundant;
// if c1 and c2 are different then it's always true or always false.
// Exprs must contain only side effect free expressions.
func (op boolOp) checkSuspect(pass *analysis.Pass, exprs []ast.Expr) {
// seen maps from expressions 'x' to equality expressions 'x != c'.
seen := make(map[string]string)
for _, e := range exprs {
bin, ok := e.(*ast.BinaryExpr)
if !ok || bin.Op != op.badEq {
continue
}
// In order to avoid false positives, restrict to cases
// in which one of the operands is constant. We're then
// interested in the other operand.
// In the rare case in which both operands are constant
// (e.g. runtime.GOOS and "windows"), we'll only catch
// mistakes if the LHS is repeated, which is how most
// code is written.
var x ast.Expr
switch {
case pass.TypesInfo.Types[bin.Y].Value != nil:
x = bin.X
case pass.TypesInfo.Types[bin.X].Value != nil:
x = bin.Y
default:
continue
}
// e is of the form 'x != c' or 'x == c'.
xfmt := astutil.Format(pass.Fset, x)
efmt := astutil.Format(pass.Fset, e)
if prev, found := seen[xfmt]; found {
// checkRedundant handles the case in which efmt == prev.
if efmt != prev {
pass.ReportRangef(e, "suspect %s: %s %s %s", op.name, efmt, op.tok, prev)
}
} else {
seen[xfmt] = efmt
}
}
}
// split returns a slice of all subexpressions in e that are connected by op.
// For example, given 'a || (b || c) || d' with the or op,
// split returns []{d, c, b, a}.
// seen[e] is already true; any newly processed exprs are added to seen.
func (op boolOp) split(e ast.Expr, seen map[*ast.BinaryExpr]bool) (exprs []ast.Expr) {
for {
e = ast.Unparen(e)
if b, ok := e.(*ast.BinaryExpr); ok && b.Op == op.tok {
seen[b] = true
exprs = append(exprs, op.split(b.Y, seen)...)
e = b.X
} else {
exprs = append(exprs, e)
break
}
}
return
}
================================================
FILE: go/analysis/passes/bools/bools_test.go
================================================
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package bools_test
import (
"testing"
"golang.org/x/tools/go/analysis/analysistest"
"golang.org/x/tools/go/analysis/passes/bools"
)
func Test(t *testing.T) {
testdata := analysistest.TestData()
analysistest.Run(t, testdata, bools.Analyzer, "a", "typeparams")
}
================================================
FILE: go/analysis/passes/bools/testdata/src/a/a.go
================================================
// Copyright 2014 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// This file contains tests for the bool checker.
package a
import "io"
type T int
func (t T) Foo() int { return int(t) }
type FT func() int
var S []int
func RatherStupidConditions() {
var f, g func() int
if f() == 0 || f() == 0 { // OK f might have side effects
}
var t T
_ = t.Foo() == 2 || t.Foo() == 2 // OK Foo might have side effects
if v, w := f(), g(); v == w || v == w { // want `redundant or: v == w \|\| v == w`
}
_ = f == nil || f == nil // want `redundant or: f == nil \|\| f == nil`
var B byte
_ = B == byte(1) || B == byte(1) // want `redundant or: B == byte\(1\) \|\| B == byte\(1\)`
_ = t == T(2) || t == T(2) // want `redundant or: t == T\(2\) \|\| t == T\(2\)`
_ = FT(f) == nil || FT(f) == nil // want `redundant or: FT\(f\) == nil \|\| FT\(f\) == nil`
_ = (func() int)(f) == nil || (func() int)(f) == nil // want `redundant or: \(func\(\) int\)\(f\) == nil \|\| \(func\(\) int\)\(f\) == nil`
_ = append(S, 3) == nil || append(S, 3) == nil // OK append has side effects
var namedFuncVar FT
_ = namedFuncVar() == namedFuncVar() // OK still func calls
var c chan int
_ = 0 == <-c || 0 == <-c // OK subsequent receives may yield different values
for i, j := <-c, <-c; i == j || i == j; i, j = <-c, <-c { // want `redundant or: i == j \|\| i == j`
}
var i, j, k int
_ = i+1 == 1 || i+1 == 1 // want `redundant or: i\+1 == 1 \|\| i\+1 == 1`
_ = i == 1 || j+1 == i || i == 1 // want `redundant or: i == 1 \|\| i == 1`
_ = i == 1 || i == 1 || f() == 1 // want `redundant or: i == 1 \|\| i == 1`
_ = i == 1 || f() == 1 || i == 1 // OK f may alter i as a side effect
_ = f() == 1 || i == 1 || i == 1 // want `redundant or: i == 1 \|\| i == 1`
// Test partition edge cases
_ = f() == 1 || i == 1 || i == 1 || j == 1 // want `redundant or: i == 1 \|\| i == 1`
_ = f() == 1 || j == 1 || i == 1 || i == 1 // want `redundant or: i == 1 \|\| i == 1`
_ = i == 1 || f() == 1 || i == 1 || i == 1 // want `redundant or: i == 1 \|\| i == 1`
_ = i == 1 || i == 1 || f() == 1 || i == 1 // want `redundant or: i == 1 \|\| i == 1`
_ = i == 1 || i == 1 || j == 1 || f() == 1 // want `redundant or: i == 1 \|\| i == 1`
_ = j == 1 || i == 1 || i == 1 || f() == 1 // want `redundant or: i == 1 \|\| i == 1`
_ = i == 1 || f() == 1 || f() == 1 || i == 1
_ = i == 1 || (i == 1 || i == 2) // want `redundant or: i == 1 \|\| i == 1`
_ = i == 1 || (f() == 1 || i == 1) // OK f may alter i as a side effect
_ = i == 1 || (i == 1 || f() == 1) // want `redundant or: i == 1 \|\| i == 1`
_ = i == 1 || (i == 2 || (i == 1 || i == 3)) // want `redundant or: i == 1 \|\| i == 1`
var a, b bool
_ = i == 1 || (a || (i == 1 || b)) // want `redundant or: i == 1 \|\| i == 1`
// Check that all redundant ors are flagged
_ = j == 0 ||
i == 1 ||
f() == 1 ||
j == 0 || // want `redundant or: j == 0 \|\| j == 0`
i == 1 || // want `redundant or: i == 1 \|\| i == 1`
i == 1 || // want `redundant or: i == 1 \|\| i == 1`
i == 1 ||
j == 0 ||
k == 0
_ = i == 1*2*3 || i == 1*2*3 // want `redundant or: i == 1\*2\*3 \|\| i == 1\*2\*3`
// These test that redundant, suspect expressions do not trigger multiple errors.
_ = i != 0 || i != 0 // want `redundant or: i != 0 \|\| i != 0`
_ = i == 0 && i == 0 // want `redundant and: i == 0 && i == 0`
// and is dual to or; check the basics and
// let the or tests pull the rest of the weight.
_ = 0 != <-c && 0 != <-c // OK subsequent receives may yield different values
_ = f() != 0 && f() != 0 // OK f might have side effects
_ = f != nil && f != nil // want `redundant and: f != nil && f != nil`
_ = i != 1 && i != 1 && f() != 1 // want `redundant and: i != 1 && i != 1`
_ = i != 1 && f() != 1 && i != 1 // OK f may alter i as a side effect
_ = f() != 1 && i != 1 && i != 1 // want `redundant and: i != 1 && i != 1`
}
func RoyallySuspectConditions() {
var i, j int
_ = i == 0 || i == 1 // OK
_ = i != 0 || i != 1 // want `suspect or: i != 0 \|\| i != 1`
_ = i != 0 || 1 != i // want `suspect or: i != 0 \|\| 1 != i`
_ = 0 != i || 1 != i // want `suspect or: 0 != i \|\| 1 != i`
_ = 0 != i || i != 1 // want `suspect or: 0 != i \|\| i != 1`
_ = (0 != i) || i != 1 // want `suspect or: 0 != i \|\| i != 1`
_ = i+3 != 7 || j+5 == 0 || i+3 != 9 // want `suspect or: i\+3 != 7 \|\| i\+3 != 9`
_ = i != 0 || j == 0 || i != 1 // want `suspect or: i != 0 \|\| i != 1`
_ = i != 0 || i != 1<<4 // want `suspect or: i != 0 \|\| i != 1<<4`
_ = i != 0 || j != 0
_ = 0 != i || 0 != j
var s string
_ = s != "one" || s != "the other" // want `suspect or: s != .one. \|\| s != .the other.`
_ = "et" != "alii" || "et" != "cetera" // want `suspect or: .et. != .alii. \|\| .et. != .cetera.`
_ = "me gustas" != "tu" || "le gustas" != "tu" // OK we could catch this case, but it's not worth the code
var err error
_ = err != nil || err != io.EOF // TODO catch this case?
// Sanity check and.
_ = i != 0 && i != 1 // OK
_ = i == 0 && i == 1 // want `suspect and: i == 0 && i == 1`
_ = i == 0 && 1 == i // want `suspect and: i == 0 && 1 == i`
_ = 0 == i && 1 == i // want `suspect and: 0 == i && 1 == i`
_ = 0 == i && i == 1 // want `suspect and: 0 == i && i == 1`
}
================================================
FILE: go/analysis/passes/bools/testdata/src/typeparams/typeparams.go
================================================
// Copyright 2014 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// This file contains tests for the bool checker.
package typeparams
type T[P interface{ ~int }] struct {
a P
}
func (t T[P]) Foo() int { return int(t.a) }
type FT[P any] func() P
func Sink[Elem any]() chan Elem {
return make(chan Elem)
}
func RedundantConditions[P interface{ int }]() {
type _f[P1 any] func() P1
var f, g _f[P]
if f() == 0 || f() == 0 { // OK f might have side effects
}
var t T[P]
_ = t.Foo() == 2 || t.Foo() == 2 // OK Foo might have side effects
if v, w := f(), g(); v == w || v == w { // want `redundant or: v == w \|\| v == w`
}
// error messages present type params correctly.
_ = t == T[P]{2} || t == T[P]{2} // want `redundant or: t == T\[P\]\{2\} \|\| t == T\[P\]\{2\}`
_ = FT[P](f) == nil || FT[P](f) == nil // want `redundant or: FT\[P\]\(f\) == nil \|\| FT\[P\]\(f\) == nil`
_ = (func() P)(f) == nil || (func() P)(f) == nil // want `redundant or: \(func\(\) P\)\(f\) == nil \|\| \(func\(\) P\)\(f\) == nil`
var tint T[int]
var fint _f[int]
_ = tint == T[int]{2} || tint == T[int]{2} // want `redundant or: tint == T\[int\]\{2\} \|\| tint\ == T\[int\]\{2\}`
_ = FT[int](fint) == nil || FT[int](fint) == nil // want `redundant or: FT\[int\]\(fint\) == nil \|\| FT\[int\]\(fint\) == nil`
_ = (func() int)(fint) == nil || (func() int)(fint) == nil // want `redundant or: \(func\(\) int\)\(fint\) == nil \|\| \(func\(\) int\)\(fint\) == nil`
c := Sink[P]()
_ = 0 == <-c || 0 == <-c // OK subsequent receives may yield different values
for i, j := <-c, <-c; i == j || i == j; i, j = <-c, <-c { // want `redundant or: i == j \|\| i == j`
}
var i, j P
_ = i == 1 || j+1 == i || i == 1 // want `redundant or: i == 1 \|\| i == 1`
_ = i == 1 || f() == 1 || i == 1 // OK f may alter i as a side effect
_ = f() == 1 || i == 1 || i == 1 // want `redundant or: i == 1 \|\| i == 1`
}
func SuspectConditions[P interface{ ~int }, S interface{ ~string }]() {
var i, j P
_ = i == 0 || i == 1 // OK
_ = i+3 != 7 || j+5 == 0 || i+3 != 9 // want `suspect or: i\+3 != 7 \|\| i\+3 != 9`
var s S
_ = s != "one" || s != "the other" // want `suspect or: s != .one. \|\| s != .the other.`
}
================================================
FILE: go/analysis/passes/buildssa/buildssa.go
================================================
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package buildssa defines an Analyzer that constructs the SSA
// representation of an error-free package and returns the set of all
// functions within it. It does not report any diagnostics itself but
// may be used as an input to other analyzers.
package buildssa
import (
"go/ast"
"go/types"
"iter"
"reflect"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/ctrlflow"
"golang.org/x/tools/go/ssa"
)
var Analyzer = &analysis.Analyzer{
Name: "buildssa",
Doc: "build SSA-form IR for later passes",
URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/buildssa",
Run: run,
Requires: []*analysis.Analyzer{ctrlflow.Analyzer},
ResultType: reflect.TypeFor[*SSA](),
// Do not add FactTypes here: SSA construction of P must not
// require SSA construction of all of P's dependencies.
// (That's why we enlist the cheaper ctrlflow pass to compute
// noreturn instead of having go/ssa + buildssa do it.)
FactTypes: nil,
}
// SSA provides SSA-form intermediate representation for all the
// source functions in the current package.
type SSA struct {
Pkg *ssa.Package
SrcFuncs []*ssa.Function
}
func run(pass *analysis.Pass) (any, error) {
cfgs := pass.ResultOf[ctrlflow.Analyzer].(*ctrlflow.CFGs)
// We must create a new Program for each Package because the
// analysis API provides no place to hang a Program shared by
// all Packages. Consequently, SSA Packages and Functions do not
// have a canonical representation across an analysis session of
// multiple packages. This is unlikely to be a problem in
// practice because the analysis API essentially forces all
// packages to be analysed independently, so any given call to
// Analysis.Run on a package will see only SSA objects belonging
// to a single Program.
// Some Analyzers may need GlobalDebug, in which case we'll have
// to set it globally, but let's wait till we need it.
mode := ssa.BuilderMode(0)
prog := ssa.NewProgram(pass.Fset, mode)
// Use the result of the ctrlflow analysis to improve the SSA CFG.
prog.SetNoReturn(cfgs.NoReturn)
// Create SSA packages for direct imports.
for _, p := range pass.Pkg.Imports() {
prog.CreatePackage(p, nil, nil, true)
}
// Create and build the primary package.
ssapkg := prog.CreatePackage(pass.Pkg, pass.Files, pass.TypesInfo, false)
ssapkg.Build()
// Compute list of source functions, including literals,
// in source order.
var funcs []*ssa.Function
for _, fn := range allFunctions(pass) {
// (init functions have distinct Func
// objects named "init" and distinct
// ssa.Functions named "init#1", ...)
f := ssapkg.Prog.FuncValue(fn)
if f == nil {
panic(fn)
}
var addAnons func(f *ssa.Function)
addAnons = func(f *ssa.Function) {
funcs = append(funcs, f)
for _, anon := range f.AnonFuncs {
addAnons(anon)
}
}
addAnons(f)
}
return &SSA{Pkg: ssapkg, SrcFuncs: funcs}, nil
}
// allFunctions returns an iterator over all named functions.
func allFunctions(pass *analysis.Pass) iter.Seq2[*ast.FuncDecl, *types.Func] {
return func(yield func(*ast.FuncDecl, *types.Func) bool) {
for _, file := range pass.Files {
for _, decl := range file.Decls {
if decl, ok := decl.(*ast.FuncDecl); ok {
fn := pass.TypesInfo.Defs[decl.Name].(*types.Func)
if !yield(decl, fn) {
return
}
}
}
}
}
}
================================================
FILE: go/analysis/passes/buildssa/buildssa_test.go
================================================
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package buildssa_test
import (
"fmt"
"os"
"testing"
"golang.org/x/tools/go/analysis/analysistest"
"golang.org/x/tools/go/analysis/passes/buildssa"
)
func Test(t *testing.T) {
testdata := analysistest.TestData()
result := analysistest.Run(t, testdata, buildssa.Analyzer, "a")[0].Result
ssainfo := result.(*buildssa.SSA)
got := fmt.Sprint(ssainfo.SrcFuncs)
want := `[a.Fib (a.T).fib a._ a._]`
if got != want {
t.Errorf("SSA.SrcFuncs = %s, want %s", got, want)
for _, f := range ssainfo.SrcFuncs {
f.WriteTo(os.Stderr)
}
}
}
func TestGenericDecls(t *testing.T) {
testdata := analysistest.TestData()
result := analysistest.Run(t, testdata, buildssa.Analyzer, "b")[0].Result
ssainfo := result.(*buildssa.SSA)
got := fmt.Sprint(ssainfo.SrcFuncs)
want := `[(*b.Pointer[T]).Load b.Load b.LoadPointer]`
if got != want {
t.Errorf("SSA.SrcFuncs = %s, want %s", got, want)
for _, f := range ssainfo.SrcFuncs {
f.WriteTo(os.Stderr)
}
}
}
func TestImporting(t *testing.T) {
testdata := analysistest.TestData()
result := analysistest.Run(t, testdata, buildssa.Analyzer, "c")[0].Result
ssainfo := result.(*buildssa.SSA)
got := fmt.Sprint(ssainfo.SrcFuncs)
want := `[c.A c.B]`
if got != want {
t.Errorf("SSA.SrcFuncs = %s, want %s", got, want)
for _, f := range ssainfo.SrcFuncs {
f.WriteTo(os.Stderr)
}
}
}
================================================
FILE: go/analysis/passes/buildssa/testdata/src/a/a.go
================================================
package a
func Fib(x int) int {
if x < 2 {
return x
}
return Fib(x-1) + Fib(x-2)
}
type T int
func (T) fib(x int) int { return Fib(x) }
func _() {
print("hi")
}
func _() {
print("hello")
}
================================================
FILE: go/analysis/passes/buildssa/testdata/src/b/b.go
================================================
// Package b contains declarations of generic functions.
package b
import "unsafe"
type Pointer[T any] struct {
v unsafe.Pointer
}
func (x *Pointer[T]) Load() *T {
return (*T)(LoadPointer(&x.v))
}
func Load[T any](x *Pointer[T]) *T {
return x.Load()
}
func LoadPointer(addr *unsafe.Pointer) (val unsafe.Pointer)
var G Pointer[int]
================================================
FILE: go/analysis/passes/buildssa/testdata/src/c/c.go
================================================
// Package c is to test buildssa importing packages.
package c
import (
"a"
"b"
"unsafe"
)
func A() {
_ = a.Fib(10)
}
func B() {
var x int
ptr := unsafe.Pointer(&x)
_ = b.LoadPointer(&ptr)
m := b.G.Load()
f := b.Load(&b.G)
if f != m {
panic("loads of b.G are expected to be identical")
}
}
================================================
FILE: go/analysis/passes/buildtag/buildtag.go
================================================
// Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package buildtag defines an Analyzer that checks build tags.
package buildtag
import (
"go/ast"
"go/build/constraint"
"go/parser"
"go/token"
"strings"
"unicode"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/internal/analysis/analyzerutil"
)
const Doc = "check //go:build and // +build directives"
var Analyzer = &analysis.Analyzer{
Name: "buildtag",
Doc: Doc,
URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/buildtag",
Run: runBuildTag,
}
func runBuildTag(pass *analysis.Pass) (any, error) {
for _, f := range pass.Files {
checkGoFile(pass, f)
}
for _, name := range pass.OtherFiles {
if err := checkOtherFile(pass, name); err != nil {
return nil, err
}
}
for _, name := range pass.IgnoredFiles {
if strings.HasSuffix(name, ".go") {
f, err := parser.ParseFile(pass.Fset, name, nil, parser.ParseComments|parser.SkipObjectResolution)
if err != nil {
// Not valid Go source code - not our job to diagnose, so ignore.
return nil, nil
}
checkGoFile(pass, f)
} else {
if err := checkOtherFile(pass, name); err != nil {
return nil, err
}
}
}
return nil, nil
}
func checkGoFile(pass *analysis.Pass, f *ast.File) {
var check checker
check.init(pass)
defer check.finish()
for _, group := range f.Comments {
// A +build comment is ignored after or adjoining the package declaration.
if group.End()+1 >= f.Package {
check.plusBuildOK = false
}
// A //go:build comment is ignored after the package declaration
// (but adjoining it is OK, in contrast to +build comments).
if group.Pos() >= f.Package {
check.goBuildOK = false
}
// Check each line of a //-comment.
for _, c := range group.List {
// "+build" is ignored within or after a /*...*/ comment.
if !strings.HasPrefix(c.Text, "//") {
check.plusBuildOK = false
}
check.comment(c.Slash, c.Text)
}
}
}
func checkOtherFile(pass *analysis.Pass, filename string) error {
var check checker
check.init(pass)
defer check.finish()
// We cannot use the Go parser, since this may not be a Go source file.
// Read the raw bytes instead.
content, tf, err := analyzerutil.ReadFile(pass, filename)
if err != nil {
return err
}
check.file(token.Pos(tf.Base()), string(content))
return nil
}
type checker struct {
pass *analysis.Pass
plusBuildOK bool // "+build" lines still OK
goBuildOK bool // "go:build" lines still OK
crossCheck bool // cross-check go:build and +build lines when done reading file
inStar bool // currently in a /* */ comment
goBuildPos token.Pos // position of first go:build line found
plusBuildPos token.Pos // position of first "+build" line found
goBuild constraint.Expr // go:build constraint found
plusBuild constraint.Expr // AND of +build constraints found
}
func (check *checker) init(pass *analysis.Pass) {
check.pass = pass
check.goBuildOK = true
check.plusBuildOK = true
check.crossCheck = true
}
func (check *checker) file(pos token.Pos, text string) {
// Determine cutpoint where +build comments are no longer valid.
// They are valid in leading // comments in the file followed by
// a blank line.
//
// This must be done as a separate pass because of the
// requirement that the comment be followed by a blank line.
var plusBuildCutoff int
fullText := text
for text != "" {
i := strings.Index(text, "\n")
if i < 0 {
i = len(text)
} else {
i++
}
offset := len(fullText) - len(text)
line := text[:i]
text = text[i:]
line = strings.TrimSpace(line)
if !strings.HasPrefix(line, "//") && line != "" {
break
}
if line == "" {
plusBuildCutoff = offset
}
}
// Process each line.
// Must stop once we hit goBuildOK == false
text = fullText
check.inStar = false
for text != "" {
i := strings.Index(text, "\n")
if i < 0 {
i = len(text)
} else {
i++
}
offset := len(fullText) - len(text)
line := text[:i]
text = text[i:]
check.plusBuildOK = offset < plusBuildCutoff
if strings.HasPrefix(line, "//") {
check.comment(pos+token.Pos(offset), line)
continue
}
// Keep looking for the point at which //go:build comments
// stop being allowed. Skip over, cut out any /* */ comments.
for {
line = strings.TrimSpace(line)
if check.inStar {
i := strings.Index(line, "*/")
if i < 0 {
line = ""
break
}
line = line[i+len("*/"):]
check.inStar = false
continue
}
if strings.HasPrefix(line, "/*") {
check.inStar = true
line = line[len("/*"):]
continue
}
break
}
if line != "" {
// Found non-comment non-blank line.
// Ends space for valid //go:build comments,
// but also ends the fraction of the file we can
// reliably parse. From this point on we might
// incorrectly flag "comments" inside multiline
// string constants or anything else (this might
// not even be a Go program). So stop.
break
}
}
}
func (check *checker) comment(pos token.Pos, text string) {
if strings.HasPrefix(text, "//") {
if strings.Contains(text, "+build") {
check.plusBuildLine(pos, text)
}
if strings.Contains(text, "//go:build") {
check.goBuildLine(pos, text)
}
}
if strings.HasPrefix(text, "/*") {
if i := strings.Index(text, "\n"); i >= 0 {
// multiline /* */ comment - process interior lines
check.inStar = true
i++
pos += token.Pos(i)
text = text[i:]
for text != "" {
i := strings.Index(text, "\n")
if i < 0 {
i = len(text)
} else {
i++
}
line := text[:i]
if strings.HasPrefix(line, "//") {
check.comment(pos, line)
}
pos += token.Pos(i)
text = text[i:]
}
check.inStar = false
}
}
}
func (check *checker) goBuildLine(pos token.Pos, line string) {
if !constraint.IsGoBuild(line) {
if !strings.HasPrefix(line, "//go:build") && constraint.IsGoBuild("//"+strings.TrimSpace(line[len("//"):])) {
check.pass.Reportf(pos, "malformed //go:build line (space between // and go:build)")
}
return
}
if !check.goBuildOK || check.inStar {
check.pass.Reportf(pos, "misplaced //go:build comment")
check.crossCheck = false
return
}
if check.goBuildPos == token.NoPos {
check.goBuildPos = pos
} else {
check.pass.Reportf(pos, "unexpected extra //go:build line")
check.crossCheck = false
}
// testing hack: stop at // ERROR
if i := strings.Index(line, " // ERROR "); i >= 0 {
line = line[:i]
}
x, err := constraint.Parse(line)
if err != nil {
check.pass.Reportf(pos, "%v", err)
check.crossCheck = false
return
}
check.tags(pos, x)
if check.goBuild == nil {
check.goBuild = x
}
}
func (check *checker) plusBuildLine(pos token.Pos, line string) {
line = strings.TrimSpace(line)
if !constraint.IsPlusBuild(line) {
// Comment with +build but not at beginning.
// Only report early in file.
if check.plusBuildOK && !strings.HasPrefix(line, "// want") {
check.pass.Reportf(pos, "possible malformed +build comment")
}
return
}
if !check.plusBuildOK { // inStar implies !plusBuildOK
check.pass.Reportf(pos, "misplaced +build comment")
check.crossCheck = false
}
if check.plusBuildPos == token.NoPos {
check.plusBuildPos = pos
}
// testing hack: stop at // ERROR
if i := strings.Index(line, " // ERROR "); i >= 0 {
line = line[:i]
}
fields := strings.Fields(line[len("//"):])
// IsPlusBuildConstraint check above implies fields[0] == "+build"
for _, arg := range fields[1:] {
for elem := range strings.SplitSeq(arg, ",") {
if strings.HasPrefix(elem, "!!") {
check.pass.Reportf(pos, "invalid double negative in build constraint: %s", arg)
check.crossCheck = false
continue
}
elem = strings.TrimPrefix(elem, "!")
for _, c := range elem {
if !unicode.IsLetter(c) && !unicode.IsDigit(c) && c != '_' && c != '.' {
check.pass.Reportf(pos, "invalid non-alphanumeric build constraint: %s", arg)
check.crossCheck = false
break
}
}
}
}
if check.crossCheck {
y, err := constraint.Parse(line)
if err != nil {
// Should never happen - constraint.Parse never rejects a // +build line.
// Also, we just checked the syntax above.
// Even so, report.
check.pass.Reportf(pos, "%v", err)
check.crossCheck = false
return
}
check.tags(pos, y)
if check.plusBuild == nil {
check.plusBuild = y
} else {
check.plusBuild = &constraint.AndExpr{X: check.plusBuild, Y: y}
}
}
}
func (check *checker) finish() {
if !check.crossCheck || check.plusBuildPos == token.NoPos || check.goBuildPos == token.NoPos {
return
}
// Have both //go:build and // +build,
// with no errors found (crossCheck still true).
// Check they match.
var want constraint.Expr
lines, err := constraint.PlusBuildLines(check.goBuild)
if err != nil {
check.pass.Reportf(check.goBuildPos, "%v", err)
return
}
for _, line := range lines {
y, err := constraint.Parse(line)
if err != nil {
// Definitely should not happen, but not the user's fault.
// Do not report.
return
}
if want == nil {
want = y
} else {
want = &constraint.AndExpr{X: want, Y: y}
}
}
if want.String() != check.plusBuild.String() {
check.pass.Reportf(check.plusBuildPos, "+build lines do not match //go:build condition")
return
}
}
// tags reports issues in go versions in tags within the expression e.
func (check *checker) tags(pos token.Pos, e constraint.Expr) {
// Use Eval to visit each tag.
_ = e.Eval(func(tag string) bool {
if malformedGoTag(tag) {
check.pass.Reportf(pos, "invalid go version %q in build constraint", tag)
}
return false // result is immaterial as Eval does not short-circuit
})
}
// malformedGoTag returns true if a tag is likely to be a malformed
// go version constraint.
func malformedGoTag(tag string) bool {
// Not a go version?
if !strings.HasPrefix(tag, "go1") {
// Check for close misspellings of the "go1." prefix.
for _, pre := range []string{"go.", "g1.", "go"} {
suffix := strings.TrimPrefix(tag, pre)
if suffix != tag && validGoVersion("go1."+suffix) {
return true
}
}
return false
}
// The tag starts with "go1" so it is almost certainly a GoVersion.
// Report it if it is not a valid build constraint.
return !validGoVersion(tag)
}
// validGoVersion reports when a tag is a valid go version.
func validGoVersion(tag string) bool {
return constraint.GoVersion(&constraint.TagExpr{Tag: tag}) != ""
}
================================================
FILE: go/analysis/passes/buildtag/buildtag_test.go
================================================
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package buildtag_test
import (
"testing"
"golang.org/x/tools/go/analysis/analysistest"
"golang.org/x/tools/go/analysis/passes/buildtag"
)
func Test(t *testing.T) {
// This test has a dedicated hack in the analysistest package:
// Because it cares about IgnoredFiles, which most analyzers
// ignore, the test framework will consider expectations in
// ignore files too, but only for this analyzer.
analysistest.Run(t, analysistest.TestData(), buildtag.Analyzer, "a", "b")
}
================================================
FILE: go/analysis/passes/buildtag/testdata/src/a/buildtag.go
================================================
// Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// This file contains tests for the buildtag checker.
// want +1 `possible malformed \+build comment`
// +builder
// +build ignore
// Mention +build // want `possible malformed \+build comment`
// want +1 `misplaced \+build comment`
// +build nospace
//go:build ok
package a
// want +1 `misplaced \+build comment`
// +build toolate
var _ = 3
var _ = `
// +build notacomment
`
================================================
FILE: go/analysis/passes/buildtag/testdata/src/a/buildtag2.go
================================================
// Copyright 2020 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build no
package a
// want +1 `misplaced \+build comment`
// +build toolate
// want +1 `misplaced //go:build comment`
//go:build toolate
var _ = `
// +build notacomment
`
================================================
FILE: go/analysis/passes/buildtag/testdata/src/a/buildtag3.go
================================================
// Copyright 2020 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// want +3 `[+]build lines do not match //go:build condition`
//go:build good
// +build bad
package a
var _ = `
// +build notacomment
`
================================================
FILE: go/analysis/passes/buildtag/testdata/src/a/buildtag4.go
================================================
// Copyright 2020 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build !(bad || worse)
// +build !bad
// +build !worse
package a
================================================
FILE: go/analysis/passes/buildtag/testdata/src/a/buildtag5.go
================================================
// Copyright 2020 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build !(bad || worse)
// +build !bad,!worse
package a
//want +1 `misplaced \+build comment`
// +build other
================================================
FILE: go/analysis/passes/buildtag/testdata/src/a/buildtag6.s
================================================
// Copyright 2020 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
#include "go_asm.h"
// ok because we cannot parse assembly files.
// +build no
================================================
FILE: go/analysis/passes/buildtag/testdata/src/a/buildtag7.s
================================================
// Copyright 2020 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build ignore
#include "go_asm.h"
// ok because we cannot parse assembly files
// the assembler would complain if we did assemble this file.
//go:build no
================================================
FILE: go/analysis/passes/buildtag/testdata/src/a/buildtag8.s
================================================
// Copyright 2020 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// want +3 `\+build lines do not match //go:build condition`
//go:build something
// +build ignore
#include "go_asm.h"
// ok because we cannot parse assembly files
// the assembler would complain if we did assemble this file.
//go:build no
================================================
FILE: go/analysis/passes/buildtag/testdata/src/b/vers.go
================================================
// Copyright 2024 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// want +3 `invalid go version \"go1.20.1\" in build constraint`
// want +1 `invalid go version \"go1.20.1\" in build constraint`
//go:build go1.20.1
// +build go1.20.1
package b
================================================
FILE: go/analysis/passes/buildtag/testdata/src/b/vers1.go
================================================
// Copyright 2024 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// This file is intentionally so its build tags always match.
package b
================================================
FILE: go/analysis/passes/buildtag/testdata/src/b/vers2.go
================================================
// Copyright 2024 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// want +1 `invalid go version \"go120\" in build constraint`
//go:build go120
package b
================================================
FILE: go/analysis/passes/buildtag/testdata/src/b/vers3.go
================================================
// Copyright 2024 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// want +1 `invalid go version \"go1..20\" in build constraint`
//go:build go1..20
package b
================================================
FILE: go/analysis/passes/buildtag/testdata/src/b/vers4.go
================================================
// Copyright 2024 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// want +1 `invalid go version \"go.20\" in build constraint`
//go:build go.20
package b
================================================
FILE: go/analysis/passes/buildtag/testdata/src/b/vers5.go
================================================
// Copyright 2024 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// want +1 `invalid go version \"g1.20\" in build constraint`
//go:build g1.20
package b
================================================
FILE: go/analysis/passes/buildtag/testdata/src/b/vers6.go
================================================
// Copyright 2024 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// want +1 `invalid go version \"go20\" in build constraint`
//go:build go20
package b
================================================
FILE: go/analysis/passes/cgocall/cgocall.go
================================================
// Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package cgocall defines an Analyzer that detects some violations of
// the cgo pointer passing rules.
package cgocall
import (
"fmt"
"go/ast"
"go/format"
"go/parser"
"go/token"
"go/types"
"log"
"os"
"strconv"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/internal/typesinternal"
)
const debug = false
const Doc = `detect some violations of the cgo pointer passing rules
Check for invalid cgo pointer passing.
This looks for code that uses cgo to call C code passing values
whose types are almost always invalid according to the cgo pointer
sharing rules.
Specifically, it warns about attempts to pass a Go chan, map, func,
or slice to C, either directly, or via a pointer, array, or struct.`
var Analyzer = &analysis.Analyzer{
Name: "cgocall",
Doc: Doc,
URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/cgocall",
RunDespiteErrors: true,
Run: run,
}
func run(pass *analysis.Pass) (any, error) {
if !typesinternal.Imports(pass.Pkg, "runtime/cgo") {
return nil, nil // doesn't use cgo
}
cgofiles, info, err := typeCheckCgoSourceFiles(pass.Fset, pass.Pkg, pass.Files, pass.TypesInfo, pass.TypesSizes)
if err != nil {
return nil, err
}
for _, f := range cgofiles {
checkCgo(pass.Fset, f, info, pass.Reportf)
}
return nil, nil
}
func checkCgo(fset *token.FileSet, f *ast.File, info *types.Info, reportf func(token.Pos, string, ...any)) {
ast.Inspect(f, func(n ast.Node) bool {
call, ok := n.(*ast.CallExpr)
if !ok {
return true
}
// Is this a C.f() call?
var name string
if sel, ok := ast.Unparen(call.Fun).(*ast.SelectorExpr); ok {
if id, ok := sel.X.(*ast.Ident); ok && id.Name == "C" {
name = sel.Sel.Name
}
}
if name == "" {
return true // not a call we need to check
}
// A call to C.CBytes passes a pointer but is always safe.
if name == "CBytes" {
return true
}
if debug {
log.Printf("%s: call to C.%s", fset.Position(call.Lparen), name)
}
for _, arg := range call.Args {
if !typeOKForCgoCall(cgoBaseType(info, arg), make(map[types.Type]bool)) {
reportf(arg.Pos(), "possibly passing Go type with embedded pointer to C")
break
}
// Check for passing the address of a bad type.
if conv, ok := arg.(*ast.CallExpr); ok && len(conv.Args) == 1 &&
isUnsafePointer(info, conv.Fun) {
arg = conv.Args[0]
}
if u, ok := arg.(*ast.UnaryExpr); ok && u.Op == token.AND {
if !typeOKForCgoCall(cgoBaseType(info, u.X), make(map[types.Type]bool)) {
reportf(arg.Pos(), "possibly passing Go type with embedded pointer to C")
break
}
}
}
return true
})
}
// typeCheckCgoSourceFiles returns type-checked syntax trees for the raw
// cgo files of a package (those that import "C"). Such files are not
// Go, so there may be gaps in type information around C.f references.
//
// This checker was initially written in vet to inspect raw cgo source
// files using partial type information. However, Analyzers in the new
// analysis API are presented with the type-checked, "cooked" Go ASTs
// resulting from cgo-processing files, so we must choose between
// working with the cooked file generated by cgo (which was tried but
// proved fragile) or locating the raw cgo file (e.g. from //line
// directives) and working with that, as we now do.
//
// Specifically, we must type-check the raw cgo source files (or at
// least the subtrees needed for this analyzer) in an environment that
// simulates the rest of the already type-checked package.
//
// For example, for each raw cgo source file in the original package,
// such as this one:
//
// package p
// import "C"
// import "fmt"
// type T int
// const k = 3
// var x, y = fmt.Println()
// func f() { ... }
// func g() { ... C.malloc(k) ... }
// func (T) f(int) string { ... }
//
// we synthesize a new ast.File, shown below, that dot-imports the
// original "cooked" package using a special name ("·this·"), so that all
// references to package members resolve correctly. (References to
// unexported names cause an "unexported" error, which we ignore.)
//
// To avoid shadowing names imported from the cooked package,
// package-level declarations in the new source file are modified so
// that they do not declare any names.
// (The cgocall analysis is concerned with uses, not declarations.)
// Specifically, type declarations are discarded;
// all names in each var and const declaration are blanked out;
// each method is turned into a regular function by turning
// the receiver into the first parameter;
// and all functions are renamed to "_".
//
// package p
// import . "·this·" // declares T, k, x, y, f, g, T.f
// import "C"
// import "fmt"
// const _ = 3
// var _, _ = fmt.Println()
// func _() { ... }
// func _() { ... C.malloc(k) ... }
// func _(T, int) string { ... }
//
// In this way, the raw function bodies and const/var initializer
// expressions are preserved but refer to the "cooked" objects imported
// from "·this·", and none of the transformed package-level declarations
// actually declares anything. In the example above, the reference to k
// in the argument of the call to C.malloc resolves to "·this·".k, which
// has an accurate type.
//
// This approach could in principle be generalized to more complex
// analyses on raw cgo files. One could synthesize a "C" package so that
// C.f would resolve to "·this·"._C_func_f, for example. But we have
// limited ourselves here to preserving function bodies and initializer
// expressions since that is all that the cgocall analyzer needs.
func typeCheckCgoSourceFiles(fset *token.FileSet, pkg *types.Package, files []*ast.File, info *types.Info, sizes types.Sizes) ([]*ast.File, *types.Info, error) {
const thispkg = "·this·"
// Which files are cgo files?
var cgoFiles []*ast.File
importMap := map[string]*types.Package{thispkg: pkg}
for _, raw := range files {
// If f is a cgo-generated file, Position reports
// the original file, honoring //line directives.
filename := fset.Position(raw.Pos()).Filename // sic: Pos, not FileStart
f, err := parser.ParseFile(fset, filename, nil, parser.SkipObjectResolution)
if err != nil {
return nil, nil, fmt.Errorf("can't parse raw cgo file: %v", err)
}
found := false
for _, spec := range f.Imports {
if spec.Path.Value == `"C"` {
found = true
break
}
}
if !found {
continue // not a cgo file
}
// Record the original import map.
for _, spec := range raw.Imports {
path, _ := strconv.Unquote(spec.Path.Value)
importMap[path] = imported(info, spec)
}
// Add special dot-import declaration:
// import . "·this·"
var decls []ast.Decl
decls = append(decls, &ast.GenDecl{
Tok: token.IMPORT,
Specs: []ast.Spec{
&ast.ImportSpec{
Name: &ast.Ident{Name: "."},
Path: &ast.BasicLit{
Kind: token.STRING,
Value: strconv.Quote(thispkg),
},
},
},
})
// Transform declarations from the raw cgo file.
for _, decl := range f.Decls {
switch decl := decl.(type) {
case *ast.GenDecl:
switch decl.Tok {
case token.TYPE:
// Discard type declarations.
continue
case token.IMPORT:
// Keep imports.
case token.VAR, token.CONST:
// Blank the declared var/const names.
for _, spec := range decl.Specs {
spec := spec.(*ast.ValueSpec)
for i := range spec.Names {
spec.Names[i].Name = "_"
}
}
}
case *ast.FuncDecl:
// Blank the declared func name.
decl.Name.Name = "_"
// Turn a method receiver: func (T) f(P) R {...}
// into regular parameter: func _(T, P) R {...}
if decl.Recv != nil {
var params []*ast.Field
params = append(params, decl.Recv.List...)
params = append(params, decl.Type.Params.List...)
decl.Type.Params.List = params
decl.Recv = nil
}
}
decls = append(decls, decl)
}
f.Decls = decls
if debug {
format.Node(os.Stderr, fset, f) // debugging
}
cgoFiles = append(cgoFiles, f)
}
if cgoFiles == nil {
return nil, nil, nil // nothing to do (can't happen?)
}
// Type-check the synthetic files.
tc := &types.Config{
FakeImportC: true,
Importer: importerFunc(func(path string) (*types.Package, error) {
return importMap[path], nil
}),
Sizes: sizes,
Error: func(error) {}, // ignore errors (e.g. unused import)
}
setGoVersion(tc, pkg)
// It's tempting to record the new types in the
// existing pass.TypesInfo, but we don't own it.
altInfo := &types.Info{
Types: make(map[ast.Expr]types.TypeAndValue),
}
tc.Check(pkg.Path(), fset, cgoFiles, altInfo)
return cgoFiles, altInfo, nil
}
// cgoBaseType tries to look through type conversions involving
// unsafe.Pointer to find the real type. It converts:
//
// unsafe.Pointer(x) => x
// *(*unsafe.Pointer)(unsafe.Pointer(&x)) => x
func cgoBaseType(info *types.Info, arg ast.Expr) types.Type {
switch arg := arg.(type) {
case *ast.CallExpr:
if len(arg.Args) == 1 && isUnsafePointer(info, arg.Fun) {
return cgoBaseType(info, arg.Args[0])
}
case *ast.StarExpr:
call, ok := arg.X.(*ast.CallExpr)
if !ok || len(call.Args) != 1 {
break
}
// Here arg is *f(v).
t := info.Types[call.Fun].Type
if t == nil {
break
}
ptr, ok := t.Underlying().(*types.Pointer)
if !ok {
break
}
// Here arg is *(*p)(v)
elem, ok := ptr.Elem().Underlying().(*types.Basic)
if !ok || elem.Kind() != types.UnsafePointer {
break
}
// Here arg is *(*unsafe.Pointer)(v)
call, ok = call.Args[0].(*ast.CallExpr)
if !ok || len(call.Args) != 1 {
break
}
// Here arg is *(*unsafe.Pointer)(f(v))
if !isUnsafePointer(info, call.Fun) {
break
}
// Here arg is *(*unsafe.Pointer)(unsafe.Pointer(v))
u, ok := call.Args[0].(*ast.UnaryExpr)
if !ok || u.Op != token.AND {
break
}
// Here arg is *(*unsafe.Pointer)(unsafe.Pointer(&v))
return cgoBaseType(info, u.X)
}
return info.Types[arg].Type
}
// typeOKForCgoCall reports whether the type of arg is OK to pass to a
// C function using cgo. This is not true for Go types with embedded
// pointers. m is used to avoid infinite recursion on recursive types.
func typeOKForCgoCall(t types.Type, m map[types.Type]bool) bool {
if t == nil || m[t] {
return true
}
m[t] = true
switch t := t.Underlying().(type) {
case *types.Chan, *types.Map, *types.Signature, *types.Slice:
return false
case *types.Pointer:
return typeOKForCgoCall(t.Elem(), m)
case *types.Array:
return typeOKForCgoCall(t.Elem(), m)
case *types.Struct:
for field := range t.Fields() {
if !typeOKForCgoCall(field.Type(), m) {
return false
}
}
}
return true
}
func isUnsafePointer(info *types.Info, e ast.Expr) bool {
t := info.Types[e].Type
return t != nil && t.Underlying() == types.Typ[types.UnsafePointer]
}
type importerFunc func(path string) (*types.Package, error)
func (f importerFunc) Import(path string) (*types.Package, error) { return f(path) }
// TODO(adonovan): make this a library function or method of Info.
func imported(info *types.Info, spec *ast.ImportSpec) *types.Package {
obj, ok := info.Implicits[spec]
if !ok {
obj = info.Defs[spec.Name] // renaming import
}
return obj.(*types.PkgName).Imported()
}
================================================
FILE: go/analysis/passes/cgocall/cgocall_go120.go
================================================
// Copyright 2023 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build !go1.21
package cgocall
import "go/types"
func setGoVersion(tc *types.Config, pkg *types.Package) {
// no types.Package.GoVersion until Go 1.21
}
================================================
FILE: go/analysis/passes/cgocall/cgocall_go121.go
================================================
// Copyright 2023 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build go1.21
package cgocall
import "go/types"
func setGoVersion(tc *types.Config, pkg *types.Package) {
tc.GoVersion = pkg.GoVersion()
}
================================================
FILE: go/analysis/passes/cgocall/cgocall_test.go
================================================
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package cgocall_test
import (
"testing"
"golang.org/x/tools/go/analysis/analysistest"
"golang.org/x/tools/go/analysis/passes/cgocall"
)
func Test(t *testing.T) {
testdata := analysistest.TestData()
analysistest.Run(t, testdata, cgocall.Analyzer, "a", "b", "c", "typeparams")
}
================================================
FILE: go/analysis/passes/cgocall/testdata/src/a/cgo.go
================================================
// Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// This file contains tests for the cgo checker.
package a
// void f(void *ptr) {}
import "C"
import "unsafe"
func CgoTests() {
var c chan bool
C.f(*(*unsafe.Pointer)(unsafe.Pointer(&c))) // want "embedded pointer"
C.f(unsafe.Pointer(&c)) // want "embedded pointer"
var m map[string]string
C.f(*(*unsafe.Pointer)(unsafe.Pointer(&m))) // want "embedded pointer"
C.f(unsafe.Pointer(&m)) // want "embedded pointer"
var f func()
C.f(*(*unsafe.Pointer)(unsafe.Pointer(&f))) // want "embedded pointer"
C.f(unsafe.Pointer(&f)) // want "embedded pointer"
var s []int
C.f(*(*unsafe.Pointer)(unsafe.Pointer(&s))) // want "embedded pointer"
C.f(unsafe.Pointer(&s)) // want "embedded pointer"
var a [1][]int
C.f(*(*unsafe.Pointer)(unsafe.Pointer(&a))) // want "embedded pointer"
C.f(unsafe.Pointer(&a)) // want "embedded pointer"
var st struct{ f []int }
C.f(*(*unsafe.Pointer)(unsafe.Pointer(&st))) // want "embedded pointer"
C.f(unsafe.Pointer(&st)) // want "embedded pointer"
var st3 S
C.f(*(*unsafe.Pointer)(unsafe.Pointer(&st3))) // want "embedded pointer"
C.f(unsafe.Pointer(&st3)) // want "embedded pointer"
// The following cases are OK.
var i int
C.f(*(*unsafe.Pointer)(unsafe.Pointer(&i)))
C.f(unsafe.Pointer(&i))
C.f(*(*unsafe.Pointer)(unsafe.Pointer(&s[0])))
C.f(unsafe.Pointer(&s[0]))
var a2 [1]int
C.f(*(*unsafe.Pointer)(unsafe.Pointer(&a2)))
C.f(unsafe.Pointer(&a2))
var st2 struct{ i int }
C.f(*(*unsafe.Pointer)(unsafe.Pointer(&st2)))
C.f(unsafe.Pointer(&st2))
var st4 S2
C.f(*(*unsafe.Pointer)(unsafe.Pointer(&st4)))
C.f(unsafe.Pointer(&st4))
type cgoStruct struct{ p *cgoStruct }
C.f(unsafe.Pointer(&cgoStruct{}))
C.CBytes([]byte("hello"))
}
type S struct{ slice []int }
type S2 struct{ int int }
================================================
FILE: go/analysis/passes/cgocall/testdata/src/a/cgo3.go
================================================
// Copyright 2017 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package a
// The purpose of this inherited test is unclear.
import "C"
const x = 1
var a, b = 1, 2
func F() {
}
func FAD(int, string) bool {
C.malloc(3)
return true
}
================================================
FILE: go/analysis/passes/cgocall/testdata/src/b/b.go
================================================
// Copyright 2017 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Test the cgo checker on a file that doesn't use cgo, but has an
// import named "C".
package b
import C "fmt"
import "unsafe"
func init() {
var f func()
C.Println(unsafe.Pointer(&f))
// Passing a pointer (via a slice), but C is fmt, not cgo.
C.Println([]int{3})
}
================================================
FILE: go/analysis/passes/cgocall/testdata/src/c/c.go
================================================
// Copyright 2016 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Test the cgo checker on a file that doesn't use cgo.
package c
import "unsafe"
// Passing a pointer (via the slice), but C isn't cgo.
var _ = C.f(unsafe.Pointer(new([]int)))
var C struct{ f func(interface{}) int }
================================================
FILE: go/analysis/passes/cgocall/testdata/src/typeparams/typeparams.go
================================================
// Copyright 2021 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// This file contains tests for the cgo checker.
package a
// void f(void *ptr) {}
import "C"
import "unsafe"
func CgoTest[T any]() {
var c chan bool
C.f(*(*unsafe.Pointer)(unsafe.Pointer(&c))) // want "embedded pointer"
C.f(unsafe.Pointer(&c)) // want "embedded pointer"
var schan S[chan bool]
C.f(*(*unsafe.Pointer)(unsafe.Pointer(&schan))) // want "embedded pointer"
C.f(unsafe.Pointer(&schan)) // want "embedded pointer"
var x T
C.f(*(*unsafe.Pointer)(unsafe.Pointer(&x))) // no findings as T is not known compile-time
C.f(unsafe.Pointer(&x))
// instantiating CgoTest should not yield any warnings
CgoTest[chan bool]()
var sint S[int]
C.f(*(*unsafe.Pointer)(unsafe.Pointer(&sint)))
C.f(unsafe.Pointer(&sint))
}
type S[X any] struct {
val X
}
================================================
FILE: go/analysis/passes/composite/composite.go
================================================
// Copyright 2012 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package composite defines an Analyzer that checks for unkeyed
// composite literals.
package composite
import (
"fmt"
"go/ast"
"go/types"
"strings"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/ast/inspector"
"golang.org/x/tools/internal/typeparams"
)
const Doc = `check for unkeyed composite literals
This analyzer reports a diagnostic for composite literals of struct
types imported from another package that do not use the field-keyed
syntax. Such literals are fragile because the addition of a new field
(even if unexported) to the struct will cause compilation to fail.
As an example,
err = &net.DNSConfigError{err}
should be replaced by:
err = &net.DNSConfigError{Err: err}
`
var Analyzer = &analysis.Analyzer{
Name: "composites",
Doc: Doc,
URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/composite",
Requires: []*analysis.Analyzer{inspect.Analyzer},
RunDespiteErrors: true,
Run: run,
}
var whitelist = true
func init() {
Analyzer.Flags.BoolVar(&whitelist, "whitelist", whitelist, "use composite white list; for testing only")
}
// runUnkeyedLiteral checks if a composite literal is a struct literal with
// unkeyed fields.
func run(pass *analysis.Pass) (any, error) {
inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
nodeFilter := []ast.Node{
(*ast.CompositeLit)(nil),
}
inspect.Preorder(nodeFilter, func(n ast.Node) {
cl := n.(*ast.CompositeLit)
typ := pass.TypesInfo.Types[cl].Type
if typ == nil {
// cannot determine composite literals' type, skip it
return
}
typeName := typ.String()
if whitelist && unkeyedLiteral[typeName] {
// skip whitelisted types
return
}
var structuralTypes []types.Type
switch typ := types.Unalias(typ).(type) {
case *types.TypeParam:
terms, err := typeparams.StructuralTerms(typ)
if err != nil {
return // invalid type
}
for _, term := range terms {
structuralTypes = append(structuralTypes, term.Type())
}
default:
structuralTypes = append(structuralTypes, typ)
}
for _, typ := range structuralTypes {
strct, ok := typeparams.Deref(typ).Underlying().(*types.Struct)
if !ok {
// skip non-struct composite literals
continue
}
if isLocalType(pass, typ) {
// allow unkeyed locally defined composite literal
continue
}
// check if the struct contains an unkeyed field
allKeyValue := true
var suggestedFixAvailable = len(cl.Elts) == strct.NumFields()
var missingKeys []analysis.TextEdit
for i, e := range cl.Elts {
if _, ok := e.(*ast.KeyValueExpr); !ok {
allKeyValue = false
if i >= strct.NumFields() {
break
}
field := strct.Field(i)
if !field.Exported() {
// Adding unexported field names for structs not defined
// locally will not work.
suggestedFixAvailable = false
break
}
missingKeys = append(missingKeys, analysis.TextEdit{
Pos: e.Pos(),
End: e.Pos(),
NewText: fmt.Appendf(nil, "%s: ", field.Name()),
})
}
}
if allKeyValue {
// all the struct fields are keyed
continue
}
diag := analysis.Diagnostic{
Pos: cl.Pos(),
End: cl.End(),
Message: fmt.Sprintf("%s struct literal uses unkeyed fields", typeName),
}
if suggestedFixAvailable {
diag.SuggestedFixes = []analysis.SuggestedFix{{
Message: "Add field names to struct literal",
TextEdits: missingKeys,
}}
}
pass.Report(diag)
return
}
})
return nil, nil
}
// isLocalType reports whether typ belongs to the same package as pass.
// TODO(adonovan): local means "internal to a function"; rename to isSamePackageType.
func isLocalType(pass *analysis.Pass, typ types.Type) bool {
switch x := types.Unalias(typ).(type) {
case *types.Struct:
// struct literals are local types
return true
case *types.Pointer:
return isLocalType(pass, x.Elem())
case interface{ Obj() *types.TypeName }: // *Named or *TypeParam (aliases were removed already)
// names in package foo are local to foo_test too
return x.Obj().Pkg() != nil &&
strings.TrimSuffix(x.Obj().Pkg().Path(), "_test") == strings.TrimSuffix(pass.Pkg.Path(), "_test")
}
return false
}
================================================
FILE: go/analysis/passes/composite/composite_test.go
================================================
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package composite_test
import (
"testing"
"golang.org/x/tools/go/analysis/analysistest"
"golang.org/x/tools/go/analysis/passes/composite"
)
func Test(t *testing.T) {
testdata := analysistest.TestData()
analysistest.RunWithSuggestedFixes(t, testdata, composite.Analyzer, "a", "typeparams")
}
================================================
FILE: go/analysis/passes/composite/testdata/src/a/a.go
================================================
// Copyright 2012 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// This file contains the test for untagged struct literals.
package a
import (
"flag"
"go/scanner"
"go/token"
"image"
"sync"
"unicode"
)
var Okay1 = []string{
"Name",
"Usage",
"DefValue",
}
var Okay2 = map[string]bool{
"Name": true,
"Usage": true,
"DefValue": true,
}
var Okay3 = struct {
X string
Y string
Z string
}{
"Name",
"Usage",
"DefValue",
}
var Okay4 = []struct {
A int
B int
}{
{1, 2},
{3, 4},
}
type MyStruct struct {
X string
Y string
Z string
}
var Okay5 = &MyStruct{
"Name",
"Usage",
"DefValue",
}
var Okay6 = []MyStruct{
{"foo", "bar", "baz"},
{"aa", "bb", "cc"},
}
var Okay7 = []*MyStruct{
{"foo", "bar", "baz"},
{"aa", "bb", "cc"},
}
// Testing is awkward because we need to reference things from a separate package
// to trigger the warnings.
var goodStructLiteral = flag.Flag{
Name: "Name",
Usage: "Usage",
}
var badStructLiteral = flag.Flag{ // want "unkeyed fields"
"Name",
"Usage",
nil, // Value
"DefValue",
}
var tooManyFieldsStructLiteral = flag.Flag{ // want "unkeyed fields"
"Name",
"Usage",
nil, // Value
"DefValue",
"Extra Field",
}
var tooFewFieldsStructLiteral = flag.Flag{ // want "unkeyed fields"
"Name",
"Usage",
nil, // Value
}
var delta [3]rune
// SpecialCase is a named slice of CaseRange to test issue 9171.
var goodNamedSliceLiteral = unicode.SpecialCase{
{Lo: 1, Hi: 2, Delta: delta},
unicode.CaseRange{Lo: 1, Hi: 2, Delta: delta},
}
var badNamedSliceLiteral = unicode.SpecialCase{
{1, 2, delta}, // want "unkeyed fields"
unicode.CaseRange{1, 2, delta}, // want "unkeyed fields"
}
// ErrorList is a named slice, so no warnings should be emitted.
var goodScannerErrorList = scanner.ErrorList{
&scanner.Error{Msg: "foobar"},
}
var badScannerErrorList = scanner.ErrorList{
&scanner.Error{token.Position{}, "foobar"}, // want "unkeyed fields"
}
// sync.Mutex has unexported fields. We expect a diagnostic but no
// suggested fix.
var mu = sync.Mutex{0, 0} // want "unkeyed fields"
// Check whitelisted structs: if vet is run with --compositewhitelist=false,
// this line triggers an error.
var whitelistedPoint = image.Point{1, 2}
// Do not check type from unknown package.
// See issue 15408.
var unknownPkgVar = unicode.NoSuchType{"foo", "bar"}
// A named pointer slice of CaseRange to test issue 23539. In
// particular, we're interested in how some slice elements omit their
// type.
var goodNamedPointerSliceLiteral = []*unicode.CaseRange{
{Lo: 1, Hi: 2},
&unicode.CaseRange{Lo: 1, Hi: 2},
}
var badNamedPointerSliceLiteral = []*unicode.CaseRange{
{1, 2, delta}, // want "unkeyed fields"
&unicode.CaseRange{1, 2, delta}, // want "unkeyed fields"
}
// unicode.Range16 is whitelisted, so there'll be no vet error
var range16 = unicode.Range16{0xfdd0, 0xfdef, 1}
// unicode.Range32 is whitelisted, so there'll be no vet error
var range32 = unicode.Range32{0x1fffe, 0x1ffff, 1}
================================================
FILE: go/analysis/passes/composite/testdata/src/a/a.go.golden
================================================
// Copyright 2012 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// This file contains the test for untagged struct literals.
package a
import (
"flag"
"go/scanner"
"go/token"
"image"
"sync"
"unicode"
)
var Okay1 = []string{
"Name",
"Usage",
"DefValue",
}
var Okay2 = map[string]bool{
"Name": true,
"Usage": true,
"DefValue": true,
}
var Okay3 = struct {
X string
Y string
Z string
}{
"Name",
"Usage",
"DefValue",
}
var Okay4 = []struct {
A int
B int
}{
{1, 2},
{3, 4},
}
type MyStruct struct {
X string
Y string
Z string
}
var Okay5 = &MyStruct{
"Name",
"Usage",
"DefValue",
}
var Okay6 = []MyStruct{
{"foo", "bar", "baz"},
{"aa", "bb", "cc"},
}
var Okay7 = []*MyStruct{
{"foo", "bar", "baz"},
{"aa", "bb", "cc"},
}
// Testing is awkward because we need to reference things from a separate package
// to trigger the warnings.
var goodStructLiteral = flag.Flag{
Name: "Name",
Usage: "Usage",
}
var badStructLiteral = flag.Flag{ // want "unkeyed fields"
Name: "Name",
Usage: "Usage",
Value: nil, // Value
DefValue: "DefValue",
}
var tooManyFieldsStructLiteral = flag.Flag{ // want "unkeyed fields"
"Name",
"Usage",
nil, // Value
"DefValue",
"Extra Field",
}
var tooFewFieldsStructLiteral = flag.Flag{ // want "unkeyed fields"
"Name",
"Usage",
nil, // Value
}
var delta [3]rune
// SpecialCase is a named slice of CaseRange to test issue 9171.
var goodNamedSliceLiteral = unicode.SpecialCase{
{Lo: 1, Hi: 2, Delta: delta},
unicode.CaseRange{Lo: 1, Hi: 2, Delta: delta},
}
var badNamedSliceLiteral = unicode.SpecialCase{
{Lo: 1, Hi: 2, Delta: delta}, // want "unkeyed fields"
unicode.CaseRange{Lo: 1, Hi: 2, Delta: delta}, // want "unkeyed fields"
}
// ErrorList is a named slice, so no warnings should be emitted.
var goodScannerErrorList = scanner.ErrorList{
&scanner.Error{Msg: "foobar"},
}
var badScannerErrorList = scanner.ErrorList{
&scanner.Error{Pos: token.Position{}, Msg: "foobar"}, // want "unkeyed fields"
}
// sync.Mutex has unexported fields. We expect a diagnostic but no
// suggested fix.
var mu = sync.Mutex{0, 0} // want "unkeyed fields"
// Check whitelisted structs: if vet is run with --compositewhitelist=false,
// this line triggers an error.
var whitelistedPoint = image.Point{1, 2}
// Do not check type from unknown package.
// See issue 15408.
var unknownPkgVar = unicode.NoSuchType{"foo", "bar"}
// A named pointer slice of CaseRange to test issue 23539. In
// particular, we're interested in how some slice elements omit their
// type.
var goodNamedPointerSliceLiteral = []*unicode.CaseRange{
{Lo: 1, Hi: 2},
&unicode.CaseRange{Lo: 1, Hi: 2},
}
var badNamedPointerSliceLiteral = []*unicode.CaseRange{
{Lo: 1, Hi: 2, Delta: delta}, // want "unkeyed fields"
&unicode.CaseRange{Lo: 1, Hi: 2, Delta: delta}, // want "unkeyed fields"
}
// unicode.Range16 is whitelisted, so there'll be no vet error
var range16 = unicode.Range16{0xfdd0, 0xfdef, 1}
// unicode.Range32 is whitelisted, so there'll be no vet error
var range32 = unicode.Range32{0x1fffe, 0x1ffff, 1}
================================================
FILE: go/analysis/passes/composite/testdata/src/a/a_fuzz_test.go
================================================
// Copyright 2022 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package a
import "testing"
var fuzzTargets = []testing.InternalFuzzTarget{
{"Fuzz", Fuzz},
}
func Fuzz(f *testing.F) {}
================================================
FILE: go/analysis/passes/composite/testdata/src/a/a_fuzz_test.go.golden
================================================
// Copyright 2022 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package a
import "testing"
var fuzzTargets = []testing.InternalFuzzTarget{
{"Fuzz", Fuzz},
}
func Fuzz(f *testing.F) {}
================================================
FILE: go/analysis/passes/composite/testdata/src/typeparams/lib/lib.go
================================================
// Copyright 2021 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package lib
type Struct struct{ F int }
type Slice []int
type Map map[int]int
================================================
FILE: go/analysis/passes/composite/testdata/src/typeparams/typeparams.go
================================================
// Copyright 2021 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package typeparams
import "typeparams/lib"
type localStruct struct{ F int }
func F[
T1 ~struct{ f int },
T2a localStruct,
T2b lib.Struct,
T3 ~[]int,
T4 lib.Slice,
T5 ~map[int]int,
T6 lib.Map,
]() {
_ = T1{2}
_ = T2a{2}
_ = T2b{2} // want "unkeyed fields"
_ = T3{1, 2}
_ = T4{1, 2}
_ = T5{1: 2}
_ = T6{1: 2}
}
================================================
FILE: go/analysis/passes/composite/testdata/src/typeparams/typeparams.go.golden
================================================
// Copyright 2021 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package typeparams
import "typeparams/lib"
type localStruct struct{ F int }
func F[
T1 ~struct{ f int },
T2a localStruct,
T2b lib.Struct,
T3 ~[]int,
T4 lib.Slice,
T5 ~map[int]int,
T6 lib.Map,
]() {
_ = T1{2}
_ = T2a{2}
_ = T2b{F: 2} // want "unkeyed fields"
_ = T3{1, 2}
_ = T4{1, 2}
_ = T5{1: 2}
_ = T6{1: 2}
}
================================================
FILE: go/analysis/passes/composite/whitelist.go
================================================
// Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package composite
// unkeyedLiteral is a white list of types in the standard packages
// that are used with unkeyed literals we deem to be acceptable.
var unkeyedLiteral = map[string]bool{
// These image and image/color struct types are frozen. We will never add fields to them.
"image/color.Alpha16": true,
"image/color.Alpha": true,
"image/color.CMYK": true,
"image/color.Gray16": true,
"image/color.Gray": true,
"image/color.NRGBA64": true,
"image/color.NRGBA": true,
"image/color.NYCbCrA": true,
"image/color.RGBA64": true,
"image/color.RGBA": true,
"image/color.YCbCr": true,
"image.Point": true,
"image.Rectangle": true,
"image.Uniform": true,
"unicode.Range16": true,
"unicode.Range32": true,
// These four structs are used in generated test main files,
// but the generator can be trusted.
"testing.InternalBenchmark": true,
"testing.InternalExample": true,
"testing.InternalTest": true,
"testing.InternalFuzzTarget": true,
}
================================================
FILE: go/analysis/passes/copylock/copylock.go
================================================
// Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package copylock defines an Analyzer that checks for locks
// erroneously passed by value.
package copylock
import (
"bytes"
"fmt"
"go/ast"
"go/token"
"go/types"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/ast/inspector"
"golang.org/x/tools/internal/astutil"
"golang.org/x/tools/internal/typeparams"
"golang.org/x/tools/internal/typesinternal"
"golang.org/x/tools/internal/versions"
)
const Doc = `check for locks erroneously passed by value
Inadvertently copying a value containing a lock, such as sync.Mutex or
sync.WaitGroup, may cause both copies to malfunction. Generally such
values should be referred to through a pointer.`
var Analyzer = &analysis.Analyzer{
Name: "copylocks",
Doc: Doc,
URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/copylock",
Requires: []*analysis.Analyzer{inspect.Analyzer},
RunDespiteErrors: true,
Run: run,
}
func run(pass *analysis.Pass) (any, error) {
inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
var goversion string // effective file version ("" => unknown)
nodeFilter := []ast.Node{
(*ast.AssignStmt)(nil),
(*ast.CallExpr)(nil),
(*ast.CompositeLit)(nil),
(*ast.File)(nil),
(*ast.FuncDecl)(nil),
(*ast.FuncLit)(nil),
(*ast.GenDecl)(nil),
(*ast.RangeStmt)(nil),
(*ast.ReturnStmt)(nil),
}
inspect.WithStack(nodeFilter, func(node ast.Node, push bool, stack []ast.Node) bool {
if !push {
return false
}
switch node := node.(type) {
case *ast.File:
goversion = versions.FileVersion(pass.TypesInfo, node)
case *ast.RangeStmt:
checkCopyLocksRange(pass, node)
case *ast.FuncDecl:
checkCopyLocksFunc(pass, node.Name.Name, node.Recv, node.Type)
case *ast.FuncLit:
checkCopyLocksFunc(pass, "func", nil, node.Type)
case *ast.CallExpr:
checkCopyLocksCallExpr(pass, node)
case *ast.AssignStmt:
checkCopyLocksAssign(pass, node, goversion, parent(stack))
case *ast.GenDecl:
checkCopyLocksGenDecl(pass, node)
case *ast.CompositeLit:
checkCopyLocksCompositeLit(pass, node)
case *ast.ReturnStmt:
checkCopyLocksReturnStmt(pass, node)
}
return true
})
return nil, nil
}
// checkCopyLocksAssign checks whether an assignment
// copies a lock.
func checkCopyLocksAssign(pass *analysis.Pass, assign *ast.AssignStmt, goversion string, parent ast.Node) {
lhs := assign.Lhs
for i, x := range assign.Rhs {
if path := lockPathRhs(pass, x); path != nil {
pass.ReportRangef(x, "assignment copies lock value to %v: %v", astutil.Format(pass.Fset, assign.Lhs[i]), path)
lhs = nil // An lhs has been reported. We prefer the assignment warning and do not report twice.
}
}
// After GoVersion 1.22, loop variables are implicitly copied on each iteration.
// So a for statement may inadvertently copy a lock when any of the
// iteration variables contain locks.
if assign.Tok == token.DEFINE && versions.AtLeast(goversion, versions.Go1_22) {
if parent, _ := parent.(*ast.ForStmt); parent != nil && parent.Init == assign {
for _, l := range lhs {
if id, ok := l.(*ast.Ident); ok && id.Name != "_" {
if obj := pass.TypesInfo.Defs[id]; obj != nil && obj.Type() != nil {
if path := lockPath(pass.Pkg, obj.Type(), nil); path != nil {
pass.ReportRangef(l, "for loop iteration copies lock value to %v: %v", astutil.Format(pass.Fset, l), path)
}
}
}
}
}
}
}
// checkCopyLocksGenDecl checks whether lock is copied
// in variable declaration.
func checkCopyLocksGenDecl(pass *analysis.Pass, gd *ast.GenDecl) {
if gd.Tok != token.VAR {
return
}
for _, spec := range gd.Specs {
valueSpec := spec.(*ast.ValueSpec)
for i, x := range valueSpec.Values {
if path := lockPathRhs(pass, x); path != nil {
pass.ReportRangef(x, "variable declaration copies lock value to %v: %v", valueSpec.Names[i].Name, path)
}
}
}
}
// checkCopyLocksCompositeLit detects lock copy inside a composite literal
func checkCopyLocksCompositeLit(pass *analysis.Pass, cl *ast.CompositeLit) {
for _, x := range cl.Elts {
if node, ok := x.(*ast.KeyValueExpr); ok {
x = node.Value
}
if path := lockPathRhs(pass, x); path != nil {
pass.ReportRangef(x, "literal copies lock value from %v: %v", astutil.Format(pass.Fset, x), path)
}
}
}
// checkCopyLocksReturnStmt detects lock copy in return statement
func checkCopyLocksReturnStmt(pass *analysis.Pass, rs *ast.ReturnStmt) {
for _, x := range rs.Results {
if path := lockPathRhs(pass, x); path != nil {
pass.ReportRangef(x, "return copies lock value: %v", path)
}
}
}
// checkCopyLocksCallExpr detects lock copy in the arguments to a function call
func checkCopyLocksCallExpr(pass *analysis.Pass, ce *ast.CallExpr) {
var id *ast.Ident
switch fun := ce.Fun.(type) {
case *ast.Ident:
id = fun
case *ast.SelectorExpr:
id = fun.Sel
}
if fun, ok := pass.TypesInfo.Uses[id].(*types.Builtin); ok {
switch fun.Name() {
case "len", "cap", "Sizeof", "Offsetof", "Alignof":
// The argument of this operation is used only
// for its type (e.g. len(array)), or the operation
// does not copy a lock (e.g. len(slice)).
return
}
}
for _, x := range ce.Args {
if path := lockPathRhs(pass, x); path != nil {
pass.ReportRangef(x, "call of %s copies lock value: %v", astutil.Format(pass.Fset, ce.Fun), path)
}
}
}
// checkCopyLocksFunc checks whether a function might
// inadvertently copy a lock, by checking whether
// its receiver, parameters, or return values
// are locks.
func checkCopyLocksFunc(pass *analysis.Pass, name string, recv *ast.FieldList, typ *ast.FuncType) {
if recv != nil && len(recv.List) > 0 {
expr := recv.List[0].Type
if path := lockPath(pass.Pkg, pass.TypesInfo.Types[expr].Type, nil); path != nil {
pass.ReportRangef(expr, "%s passes lock by value: %v", name, path)
}
}
if typ.Params != nil {
for _, field := range typ.Params.List {
expr := field.Type
if path := lockPath(pass.Pkg, pass.TypesInfo.Types[expr].Type, nil); path != nil {
pass.ReportRangef(expr, "%s passes lock by value: %v", name, path)
}
}
}
// Don't check typ.Results. If T has a Lock field it's OK to write
// return T{}
// because that is returning the zero value. Leave result checking
// to the return statement.
}
// checkCopyLocksRange checks whether a range statement
// might inadvertently copy a lock by checking whether
// any of the range variables are locks.
func checkCopyLocksRange(pass *analysis.Pass, r *ast.RangeStmt) {
checkCopyLocksRangeVar(pass, r.Tok, r.Key)
checkCopyLocksRangeVar(pass, r.Tok, r.Value)
}
func checkCopyLocksRangeVar(pass *analysis.Pass, rtok token.Token, e ast.Expr) {
if e == nil {
return
}
id, isId := e.(*ast.Ident)
if isId && id.Name == "_" {
return
}
var typ types.Type
if rtok == token.DEFINE {
if !isId {
return
}
obj := pass.TypesInfo.Defs[id]
if obj == nil {
return
}
typ = obj.Type()
} else {
typ = pass.TypesInfo.Types[e].Type
}
if typ == nil {
return
}
if path := lockPath(pass.Pkg, typ, nil); path != nil {
pass.Reportf(e.Pos(), "range var %s copies lock: %v", astutil.Format(pass.Fset, e), path)
}
}
type typePath []string
// String pretty-prints a typePath.
func (path typePath) String() string {
n := len(path)
var buf bytes.Buffer
for i := range path {
if i > 0 {
fmt.Fprint(&buf, " contains ")
}
// The human-readable path is in reverse order, outermost to innermost.
fmt.Fprint(&buf, path[n-i-1])
}
return buf.String()
}
func lockPathRhs(pass *analysis.Pass, x ast.Expr) typePath {
x = ast.Unparen(x) // ignore parens on rhs
if _, ok := x.(*ast.CompositeLit); ok {
return nil
}
if _, ok := x.(*ast.CallExpr); ok {
// A call may return a zero value.
return nil
}
if star, ok := x.(*ast.StarExpr); ok {
if _, ok := ast.Unparen(star.X).(*ast.CallExpr); ok {
// A call may return a pointer to a zero value.
return nil
}
}
if tv, ok := pass.TypesInfo.Types[x]; ok && tv.IsValue() {
return lockPath(pass.Pkg, tv.Type, nil)
}
return nil
}
// lockPath returns a typePath describing the location of a lock value
// contained in typ. If there is no contained lock, it returns nil.
//
// The seen map is used to short-circuit infinite recursion due to type cycles.
func lockPath(tpkg *types.Package, typ types.Type, seen map[types.Type]bool) typePath {
if typ == nil || seen[typ] {
return nil
}
if seen == nil {
seen = make(map[types.Type]bool)
}
seen[typ] = true
if tpar, ok := types.Unalias(typ).(*types.TypeParam); ok {
terms, err := typeparams.StructuralTerms(tpar)
if err != nil {
return nil // invalid type
}
for _, term := range terms {
subpath := lockPath(tpkg, term.Type(), seen)
if len(subpath) > 0 {
if term.Tilde() {
// Prepend a tilde to our lock path entry to clarify the resulting
// diagnostic message. Consider the following example:
//
// func _[Mutex interface{ ~sync.Mutex; M() }](m Mutex) {}
//
// Here the naive error message will be something like "passes lock
// by value: Mutex contains sync.Mutex". This is misleading because
// the local type parameter doesn't actually contain sync.Mutex,
// which lacks the M method.
//
// With tilde, it is clearer that the containment is via an
// approximation element.
subpath[len(subpath)-1] = "~" + subpath[len(subpath)-1]
}
return append(subpath, typ.String())
}
}
return nil
}
for {
atyp, ok := typ.Underlying().(*types.Array)
if !ok {
break
}
typ = atyp.Elem()
}
ttyp, ok := typ.Underlying().(*types.Tuple)
if ok {
for v := range ttyp.Variables() {
subpath := lockPath(tpkg, v.Type(), seen)
if subpath != nil {
return append(subpath, typ.String())
}
}
return nil
}
// We're only interested in the case in which the underlying
// type is a struct. (Interfaces and pointers are safe to copy.)
styp, ok := typ.Underlying().(*types.Struct)
if !ok {
return nil
}
// We're looking for cases in which a pointer to this type
// is a sync.Locker, but a value is not. This differentiates
// embedded interfaces from embedded values.
if types.Implements(types.NewPointer(typ), lockerType) && !types.Implements(typ, lockerType) {
return []string{typ.String()}
}
// In go1.10, sync.noCopy did not implement Locker.
// (The Unlock method was added only in CL 121876.)
// TODO(adonovan): remove workaround when we drop go1.10.
if typesinternal.IsTypeNamed(typ, "sync", "noCopy") {
return []string{typ.String()}
}
nfields := styp.NumFields()
for i := range nfields {
ftyp := styp.Field(i).Type()
subpath := lockPath(tpkg, ftyp, seen)
if subpath != nil {
return append(subpath, typ.String())
}
}
return nil
}
// parent returns the second from the last node on stack if it exists.
func parent(stack []ast.Node) ast.Node {
if len(stack) >= 2 {
return stack[len(stack)-2]
}
return nil
}
var lockerType *types.Interface
// Construct a sync.Locker interface type.
func init() {
nullary := types.NewSignatureType(nil, nil, nil, nil, nil, false) // func()
methods := []*types.Func{
types.NewFunc(token.NoPos, nil, "Lock", nullary),
types.NewFunc(token.NoPos, nil, "Unlock", nullary),
}
lockerType = types.NewInterface(methods, nil).Complete()
}
================================================
FILE: go/analysis/passes/copylock/copylock_test.go
================================================
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package copylock_test
import (
"path/filepath"
"testing"
"golang.org/x/tools/go/analysis/analysistest"
"golang.org/x/tools/go/analysis/passes/copylock"
"golang.org/x/tools/internal/testfiles"
)
func Test(t *testing.T) {
testdata := analysistest.TestData()
analysistest.Run(t, testdata, copylock.Analyzer, "a", "typeparams", "issue67787", "unfortunate")
}
func TestVersions22(t *testing.T) {
dir := testfiles.ExtractTxtarFileToTmp(t, filepath.Join(analysistest.TestData(), "src", "forstmt", "go22.txtar"))
analysistest.Run(t, dir, copylock.Analyzer, "golang.org/fake/forstmt")
}
func TestVersions21(t *testing.T) {
dir := testfiles.ExtractTxtarFileToTmp(t, filepath.Join(analysistest.TestData(), "src", "forstmt", "go21.txtar"))
analysistest.Run(t, dir, copylock.Analyzer, "golang.org/fake/forstmt")
}
================================================
FILE: go/analysis/passes/copylock/main.go
================================================
// Copyright 2024 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build ignore
// The copylock command applies the golang.org/x/tools/go/analysis/passes/copylock
// analysis to the specified packages of Go source code.
package main
import (
"golang.org/x/tools/go/analysis/passes/copylock"
"golang.org/x/tools/go/analysis/singlechecker"
)
func main() { singlechecker.Main(copylock.Analyzer) }
================================================
FILE: go/analysis/passes/copylock/testdata/src/a/copylock.go
================================================
package a
import (
"sync"
"sync/atomic"
"unsafe"
. "unsafe"
unsafe1 "unsafe"
)
func OkFunc() {
var x *sync.Mutex
p := x
var y sync.Mutex
p = &y
var z = sync.Mutex{}
w := sync.Mutex{}
w = sync.Mutex{}
q := struct{ L sync.Mutex }{
L: sync.Mutex{},
}
yy := []Tlock{
Tlock{},
Tlock{
once: sync.Once{},
},
}
nl := new(sync.Mutex)
mx := make([]sync.Mutex, 10)
xx := struct{ L *sync.Mutex }{
L: new(sync.Mutex),
}
var pz = (sync.Mutex{})
pw := (sync.Mutex{})
}
type Tlock struct {
once sync.Once
}
func BadFunc() {
var x *sync.Mutex
p := x
var y sync.Mutex
p = &y
*p = *x // want `assignment copies lock value to \*p: sync.Mutex`
var t Tlock
var tp *Tlock
tp = &t
*tp = t // want `assignment copies lock value to \*tp: a.Tlock contains sync.Once contains sync\b.*`
t = *tp // want `assignment copies lock value to t: a.Tlock contains sync.Once contains sync\b.*`
y := *x // want "assignment copies lock value to y: sync.Mutex"
var z = t // want `variable declaration copies lock value to z: a.Tlock contains sync.Once contains sync\b.*`
w := struct{ L sync.Mutex }{
L: *x, // want `literal copies lock value from \*x: sync.Mutex`
}
var q = map[int]Tlock{
1: t, // want `literal copies lock value from t: a.Tlock contains sync.Once contains sync\b.*`
2: *tp, // want `literal copies lock value from \*tp: a.Tlock contains sync.Once contains sync\b.*`
}
yy := []Tlock{
t, // want `literal copies lock value from t: a.Tlock contains sync.Once contains sync\b.*`
*tp, // want `literal copies lock value from \*tp: a.Tlock contains sync.Once contains sync\b.*`
}
// override 'new' keyword
new := func(interface{}) {}
new(t) // want `call of new copies lock value: a.Tlock contains sync.Once contains sync\b.*`
// copy of array of locks
var muA [5]sync.Mutex
muB := muA // want "assignment copies lock value to muB: sync.Mutex"
muA = muB // want "assignment copies lock value to muA: sync.Mutex"
muSlice := muA[:] // OK
// multidimensional array
var mmuA [5][5]sync.Mutex
mmuB := mmuA // want "assignment copies lock value to mmuB: sync.Mutex"
mmuA = mmuB // want "assignment copies lock value to mmuA: sync.Mutex"
mmuSlice := mmuA[:] // OK
// slice copy is ok
var fmuA [5][][5]sync.Mutex
fmuB := fmuA // OK
fmuA = fmuB // OK
fmuSlice := fmuA[:] // OK
// map access by single and tuple copies prohibited
type mut struct{ mu sync.Mutex }
muM := map[string]mut{
"a": mut{},
}
mumA := muM["a"] // want "assignment copies lock value to mumA: a.mut contains sync.Mutex"
mumB, _ := muM["a"] // want "assignment copies lock value to mumB: \\(a.mut, bool\\) contains a.mut contains sync.Mutex"
}
func LenAndCapOnLockArrays() {
var a [5]sync.Mutex
aLen := len(a) // OK
aCap := cap(a) // OK
// override 'len' and 'cap' keywords
len := func(interface{}) {}
len(a) // want "call of len copies lock value: sync.Mutex"
cap := func(interface{}) {}
cap(a) // want "call of cap copies lock value: sync.Mutex"
}
func SizeofMutex() {
var mu sync.Mutex
unsafe.Sizeof(mu) // OK
unsafe1.Sizeof(mu) // OK
Sizeof(mu) // OK
unsafe := struct{ Sizeof func(interface{}) }{}
unsafe.Sizeof(mu) // want "call of unsafe.Sizeof copies lock value: sync.Mutex"
Sizeof := func(interface{}) {}
Sizeof(mu) // want "call of Sizeof copies lock value: sync.Mutex"
}
func OffsetofMutex() {
type T struct {
f int
mu sync.Mutex
}
unsafe.Offsetof(T{}.mu) // OK
unsafe := struct{ Offsetof func(interface{}) }{}
unsafe.Offsetof(T{}.mu) // want "call of unsafe.Offsetof copies lock value: sync.Mutex"
}
func AlignofMutex() {
type T struct {
f int
mu sync.Mutex
}
unsafe.Alignof(T{}.mu) // OK
unsafe := struct{ Alignof func(interface{}) }{}
unsafe.Alignof(T{}.mu) // want "call of unsafe.Alignof copies lock value: sync.Mutex"
}
// SyncTypesCheck checks copying of sync.* types except sync.Mutex
func SyncTypesCheck() {
// sync.RWMutex copying
var rwmuX sync.RWMutex
var rwmuXX = sync.RWMutex{}
rwmuX1 := new(sync.RWMutex)
rwmuY := rwmuX // want "assignment copies lock value to rwmuY: sync.RWMutex"
rwmuY = rwmuX // want "assignment copies lock value to rwmuY: sync.RWMutex"
var rwmuYY = rwmuX // want "variable declaration copies lock value to rwmuYY: sync.RWMutex"
rwmuP := &rwmuX
rwmuZ := &sync.RWMutex{}
// sync.Cond copying
var condX sync.Cond
var condXX = sync.Cond{}
condX1 := new(sync.Cond)
condY := condX // want "assignment copies lock value to condY: sync.Cond contains sync.noCopy"
condY = condX // want "assignment copies lock value to condY: sync.Cond contains sync.noCopy"
var condYY = condX // want "variable declaration copies lock value to condYY: sync.Cond contains sync.noCopy"
condP := &condX
condZ := &sync.Cond{
L: &sync.Mutex{},
}
condZ = sync.NewCond(&sync.Mutex{})
// sync.WaitGroup copying
var wgX sync.WaitGroup
var wgXX = sync.WaitGroup{}
wgX1 := new(sync.WaitGroup)
wgY := wgX // want "assignment copies lock value to wgY: sync.WaitGroup contains sync.noCopy"
wgY = wgX // want "assignment copies lock value to wgY: sync.WaitGroup contains sync.noCopy"
var wgYY = wgX // want "variable declaration copies lock value to wgYY: sync.WaitGroup contains sync.noCopy"
wgP := &wgX
wgZ := &sync.WaitGroup{}
// sync.Pool copying
var poolX sync.Pool
var poolXX = sync.Pool{}
poolX1 := new(sync.Pool)
poolY := poolX // want "assignment copies lock value to poolY: sync.Pool contains sync.noCopy"
poolY = poolX // want "assignment copies lock value to poolY: sync.Pool contains sync.noCopy"
var poolYY = poolX // want "variable declaration copies lock value to poolYY: sync.Pool contains sync.noCopy"
poolP := &poolX
poolZ := &sync.Pool{}
// sync.Once copying
var onceX sync.Once
var onceXX = sync.Once{}
onceX1 := new(sync.Once)
onceY := onceX // want `assignment copies lock value to onceY: sync.Once contains sync\b.*`
onceY = onceX // want `assignment copies lock value to onceY: sync.Once contains sync\b.*`
var onceYY = onceX // want `variable declaration copies lock value to onceYY: sync.Once contains sync\b.*`
onceP := &onceX
onceZ := &sync.Once{}
}
// AtomicTypesCheck checks copying of sync/atomic types
func AtomicTypesCheck() {
// atomic.Value copying
var vX atomic.Value
var vXX = atomic.Value{}
vX1 := new(atomic.Value)
// These are OK because the value has not been used yet.
// (And vet can't tell whether it has been used, so they're always OK.)
vY := vX
vY = vX
var vYY = vX
vP := &vX
vZ := &atomic.Value{}
}
// PointerRhsCheck checks that exceptions are made for pointer return values of
// function calls. These may be zero initialized so they are considered OK.
func PointerRhsCheck() {
newMutex := func() *sync.Mutex { return new(sync.Mutex) }
d := *newMutex()
pd := *(newMutex())
}
================================================
FILE: go/analysis/passes/copylock/testdata/src/a/copylock_func.go
================================================
// Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// This file contains tests for the copylock checker's
// function declaration analysis.
// There are two cases missing from this file which
// are located in the "unfortunate" package in the
// testdata directory. Once the go.mod >= 1.26 for this
// repository, merge local_go124.go back into this file.
package a
import "sync"
func OkFunc(*sync.Mutex) {}
func BadFunc(sync.Mutex) {} // want "BadFunc passes lock by value: sync.Mutex"
func BadFunc2(sync.Map) {} // want "BadFunc2 passes lock by value: sync.Map contains sync.(Mutex|noCopy)"
func OkRet() *sync.Mutex {}
func BadRet() sync.Mutex {} // Don't warn about results
var (
OkClosure = func(*sync.Mutex) {}
BadClosure = func(sync.Mutex) {} // want "func passes lock by value: sync.Mutex"
BadClosure2 = func(sync.Map) {} // want "func passes lock by value: sync.Map contains sync.(Mutex|noCopy)"
)
type EmbeddedRWMutex struct {
sync.RWMutex
}
func (*EmbeddedRWMutex) OkMeth() {}
func (EmbeddedRWMutex) BadMeth() {} // want "BadMeth passes lock by value: a.EmbeddedRWMutex"
func OkFunc(e *EmbeddedRWMutex) {}
func BadFunc(EmbeddedRWMutex) {} // want "BadFunc passes lock by value: a.EmbeddedRWMutex"
func OkRet() *EmbeddedRWMutex {}
func BadRet() EmbeddedRWMutex {} // Don't warn about results
type FieldMutex struct {
s sync.Mutex
}
func (*FieldMutex) OkMeth() {}
func (FieldMutex) BadMeth() {} // want "BadMeth passes lock by value: a.FieldMutex contains sync.Mutex"
func OkFunc(*FieldMutex) {}
func BadFunc(FieldMutex, int) {} // want "BadFunc passes lock by value: a.FieldMutex contains sync.Mutex"
type L0 struct {
L1
}
type L1 struct {
l L2
}
type L2 struct {
sync.Mutex
}
func (*L0) Ok() {}
func (L0) Bad() {} // want "Bad passes lock by value: a.L0 contains a.L1 contains a.L2"
type EmbeddedMutexPointer struct {
s *sync.Mutex // safe to copy this pointer
}
func (*EmbeddedMutexPointer) Ok() {}
func (EmbeddedMutexPointer) AlsoOk() {}
func StillOk(EmbeddedMutexPointer) {}
func LookinGood() EmbeddedMutexPointer {}
type EmbeddedLocker struct {
sync.Locker // safe to copy interface values
}
func (*EmbeddedLocker) Ok() {}
func (EmbeddedLocker) AlsoOk() {}
type CustomLock struct{}
func (*CustomLock) Lock() {}
func (*CustomLock) Unlock() {}
func Ok(*CustomLock) {}
func Bad(CustomLock) {} // want "Bad passes lock by value: a.CustomLock"
// Passing lock values into interface function arguments
func FuncCallInterfaceArg(f func(a int, b interface{})) {
var m sync.Mutex
var t struct{ lock sync.Mutex }
f(1, "foo")
f(2, &t)
f(3, &sync.Mutex{})
f(4, m) // want "call of f copies lock value: sync.Mutex"
f(5, t) // want "call of f copies lock value: struct.lock sync.Mutex. contains sync.Mutex"
var fntab []func(t)
fntab[0](t) // want "call of fntab.0. copies lock value: struct.lock sync.Mutex. contains sync.Mutex"
}
// Returning lock via interface value
func ReturnViaInterface(x int) (int, interface{}) {
var m sync.Mutex
var t struct{ lock sync.Mutex }
switch x % 4 {
case 0:
return 0, "qwe"
case 1:
return 1, &sync.Mutex{}
case 2:
return 2, m // want "return copies lock value: sync.Mutex"
default:
return 3, t // want "return copies lock value: struct.lock sync.Mutex. contains sync.Mutex"
}
}
// Some cases that we don't warn about.
func AcceptedCases() {
x := EmbeddedRwMutex{} // composite literal on RHS is OK (#16227)
x = BadRet() // function call on RHS is OK (#16227)
x = *OKRet() // indirection of function call on RHS is OK (#16227)
}
================================================
FILE: go/analysis/passes/copylock/testdata/src/a/copylock_range.go
================================================
// Copyright 2014 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// This file contains tests for the copylock checker's
// range statement analysis.
package a
import "sync"
func rangeMutex() {
var mu sync.Mutex
var i int
var s []sync.Mutex
for range s {
}
for i = range s {
}
for i := range s {
}
for i, _ = range s {
}
for i, _ := range s {
}
for _, mu = range s { // want "range var mu copies lock: sync.Mutex"
}
for _, m := range s { // want "range var m copies lock: sync.Mutex"
}
for i, mu = range s { // want "range var mu copies lock: sync.Mutex"
}
for i, m := range s { // want "range var m copies lock: sync.Mutex"
}
var a [3]sync.Mutex
for _, m := range a { // want "range var m copies lock: sync.Mutex"
}
var m map[sync.Mutex]sync.Mutex
for k := range m { // want "range var k copies lock: sync.Mutex"
}
for mu, _ = range m { // want "range var mu copies lock: sync.Mutex"
}
for k, _ := range m { // want "range var k copies lock: sync.Mutex"
}
for _, mu = range m { // want "range var mu copies lock: sync.Mutex"
}
for _, v := range m { // want "range var v copies lock: sync.Mutex"
}
var c chan sync.Mutex
for range c {
}
for mu = range c { // want "range var mu copies lock: sync.Mutex"
}
for v := range c { // want "range var v copies lock: sync.Mutex"
}
// Test non-idents in range variables
var t struct {
i int
mu sync.Mutex
}
for t.i, t.mu = range s { // want "range var t.mu copies lock: sync.Mutex"
}
}
================================================
FILE: go/analysis/passes/copylock/testdata/src/a/issue61678.go
================================================
// This test relies on a compiler bug patched in Go 1.26.
//go:build !go1.26
package a
import "sync"
// These examples are taken from golang/go#61678, modified so that A and B
// contain a mutex.
type A struct {
a A
mu sync.Mutex
}
type B struct {
a A
b B
mu sync.Mutex
}
func okay(x A) {}
func sure() { var x A; nop(x) }
var fine B
func what(x B) {} // want `passes lock by value`
func bad() { var x B; nop(x) } // want `copies lock value`
func good() { nop(B{}) }
func stillgood() { nop(B{b: B{b: B{b: B{}}}}) }
func nope() { nop(B{}.b) } // want `copies lock value`
func nop(any) {} // only used to get around unused variable errors
================================================
FILE: go/analysis/passes/copylock/testdata/src/a/newexpr_go126.go
================================================
// Copyright 2025 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build go1.26
package a
import "sync"
func _(ptr *sync.Mutex) {
_ = new(sync.Mutex)
_ = new(*ptr) // want `call of new copies lock value: sync.Mutex`
)
================================================
FILE: go/analysis/passes/copylock/testdata/src/forstmt/go21.txtar
================================================
Test copylock at go version go1.21.
-- go.mod --
module golang.org/fake/forstmt
go 1.21
-- pre.go --
//go:build go1.21
package forstmt
import "sync"
func InGo21(l []int) {
var mu sync.Mutex
var x int
for x, mu = 0, (sync.Mutex{}); x < 10; x++ { // Not reported on '='.
}
for x, mu := 0, (sync.Mutex{}); x < 10; x++ { // Not reported before 1.22.
_ = mu.TryLock()
}
for x, _ := 0, (sync.Mutex{}); x < 10; x++ { // Not reported due to '_'.
_ = mu.TryLock()
}
for _, mu := 0, (sync.Mutex{}); x < 10; x++ { // Not reported before 1.22.
_ = mu.TryLock()
}
}
-- go22.go --
//go:build go1.22
package forstmt
import "sync"
func InGo22(l []int) {
var mu sync.Mutex
var x int
for x, mu = 0, (sync.Mutex{}); x < 10; x++ { // Not reported on '='.
}
for x, mu := 0, (sync.Mutex{}); x < 10; x++ { // want "for loop iteration copies lock value to mu: sync.Mutex"
_ = mu.TryLock()
}
for x, _ := 0, (sync.Mutex{}); x < 10; x++ { // Not reported due to '_'.
_ = mu.TryLock()
}
for _, mu := 0, (sync.Mutex{}); x < 10; x++ { // want "for loop iteration copies lock value to mu: sync.Mutex"
_ = mu.TryLock()
}
}
-- modver.go --
package forstmt
import "sync"
func AtGo121ByModuleVersion(l []int) {
var mu sync.Mutex
var x int
for x, mu = 0, (sync.Mutex{}); x < 10; x++ { // Not reported on '='.
}
for x, mu := 0, (sync.Mutex{}); x < 10; x++ { // Not reported before 1.22.
_ = mu.TryLock()
}
for x, _ := 0, (sync.Mutex{}); x < 10; x++ { // Not reported due to '_'.
_ = mu.TryLock()
}
for _, mu := 0, (sync.Mutex{}); x < 10; x++ { // Not reported before 1.22.
_ = mu.TryLock()
}
}
================================================
FILE: go/analysis/passes/copylock/testdata/src/forstmt/go22.txtar
================================================
Test copylock at go version go1.22.
-- go.mod --
module golang.org/fake/forstmt
go 1.22
-- pre.go --
//go:build go1.21
package forstmt
import "sync"
func InGo21(l []int) {
var mu sync.Mutex
var x int
for x, mu = 0, (sync.Mutex{}); x < 10; x++ { // Not reported on '='.
}
for x, mu := 0, (sync.Mutex{}); x < 10; x++ { // Not reported before 1.22.
_ = mu.TryLock()
}
for x, _ := 0, (sync.Mutex{}); x < 10; x++ { // Not reported due to '_'.
_ = mu.TryLock()
}
for _, mu := 0, (sync.Mutex{}); x < 10; x++ { // Not reported before 1.22.
_ = mu.TryLock()
}
}
-- go22.go --
//go:build go1.22
package forstmt
import "sync"
func InGo22(l []int) {
var mu sync.Mutex
var x int
for x, mu = 0, (sync.Mutex{}); x < 10; x++ { // Not reported on '='.
}
for x, mu := 0, (sync.Mutex{}); x < 10; x++ { // want "for loop iteration copies lock value to mu: sync.Mutex"
_ = mu.TryLock()
}
for x, _ := 0, (sync.Mutex{}); x < 10; x++ { // Not reported due to '_'.
_ = mu.TryLock()
}
for _, mu := 0, (sync.Mutex{}); x < 10; x++ { // want "for loop iteration copies lock value to mu: sync.Mutex"
_ = mu.TryLock()
}
}
-- modver.go --
package forstmt
import "sync"
func InGo22ByModuleVersion(l []int) {
var mu sync.Mutex
var x int
for x, mu = 0, (sync.Mutex{}); x < 10; x++ { // Not reported on '='.
}
for x, mu := 0, (sync.Mutex{}); x < 10; x++ { // want "for loop iteration copies lock value to mu: sync.Mutex"
_ = mu.TryLock()
}
for x, _ := 0, (sync.Mutex{}); x < 10; x++ { // Not reported due to '_'.
_ = mu.TryLock()
}
for _, mu := 0, (sync.Mutex{}); x < 10; x++ { // want "for loop iteration copies lock value to mu: sync.Mutex"
_ = mu.TryLock()
}
}
-- assign.go --
//go:build go1.22
package forstmt
import "sync"
func ReportAssign(l []int) {
// Test we do not report a duplicate if the assignment is reported.
var mu sync.Mutex
for x, mu := 0, mu; x < 10; x++ { // want "assignment copies lock value to mu: sync.Mutex"
_ = mu.TryLock()
}
}
================================================
FILE: go/analysis/passes/copylock/testdata/src/issue67787/issue67787.go
================================================
package issue67787
import "sync"
type T struct{ mu sync.Mutex }
type T1 struct{ t *T }
func NewT1() *T1 { return &T1{T} } // no analyzer diagnostic about T
================================================
FILE: go/analysis/passes/copylock/testdata/src/typeparams/typeparams.go
================================================
// Copyright 2021 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package typeparams
import "sync"
// The copylock analyzer runs despite errors. The following invalid type should
// not cause an infinite recursion.
type R struct{ r R }
func TestNoRecursion(r R) {}
// The following recursive type parameter definitions should not cause an
// infinite recursion.
func TestNoTypeParamRecursion[T1 ~[]T2, T2 ~[]T1 | string, T3 ~struct{ F T3 }](t1 T1, t2 T2, t3 T3) {
}
func OkFunc1[Struct ~*struct{ mu sync.Mutex }](s Struct) {
}
func BadFunc1[Struct ~struct{ mu sync.Mutex }](s Struct) { // want `passes lock by value: .*Struct contains ~struct{mu sync.Mutex}`
}
func OkFunc2[MutexPtr *sync.Mutex](m MutexPtr) {
var x *MutexPtr
p := x
var y MutexPtr
p = &y
*p = *x
var mus []MutexPtr
for _, _ = range mus {
}
}
func BadFunc2[Mutex sync.Mutex](m Mutex) { // want `passes lock by value: .*Mutex contains sync.Mutex`
var x *Mutex
p := x
var y Mutex
p = &y
*p = *x // want `assignment copies lock value to \*p: .*Mutex contains sync.Mutex`
var mus []Mutex
for _, _ = range mus {
}
}
func ApproximationError[Mutex interface {
~sync.Mutex
M()
}](m Mutex) { // want `passes lock by value: .*Mutex contains ~sync.Mutex`
}
================================================
FILE: go/analysis/passes/copylock/testdata/src/unfortunate/local_go123.go
================================================
// Copyright 2024 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build !go1.24
package unfortunate
import "sync"
// TODO: Unfortunate cases
// Non-ideal error message:
// Since we're looking for Lock methods, sync.Once's underlying
// sync.Mutex gets called out, but without any reference to the sync.Once.
type LocalOnce sync.Once
func (LocalOnce) Bad() {} // want `Bad passes lock by value: unfortunate.LocalOnce contains sync.\b.*`
// False negative:
// LocalMutex doesn't have a Lock method.
// Nevertheless, it is probably a bad idea to pass it by value.
type LocalMutex sync.Mutex
func (LocalMutex) Bad() {} // WANTED: An error here :(
================================================
FILE: go/analysis/passes/copylock/testdata/src/unfortunate/local_go124.go
================================================
// Copyright 2024 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build go1.24
package unfortunate
import "sync"
// Cases where the interior sync.noCopy shows through.
type LocalOnce sync.Once
func (LocalOnce) Bad() {} // want "Bad passes lock by value: unfortunate.LocalOnce contains sync.noCopy"
type LocalMutex sync.Mutex
func (LocalMutex) Bad() {} // want "Bad passes lock by value: unfortunate.LocalMutex contains sync.noCopy"
================================================
FILE: go/analysis/passes/ctrlflow/ctrlflow.go
================================================
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package ctrlflow is an analysis that provides a syntactic
// control-flow graph (CFG) for the body of a function.
// It records whether a function cannot return.
// By itself, it does not report any diagnostics.
package ctrlflow
import (
"go/ast"
"go/types"
"log"
"reflect"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/ast/inspector"
"golang.org/x/tools/go/cfg"
"golang.org/x/tools/go/types/typeutil"
"golang.org/x/tools/internal/typesinternal"
)
var Analyzer = &analysis.Analyzer{
Name: "ctrlflow",
Doc: "build a control-flow graph",
URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/ctrlflow",
Run: run,
ResultType: reflect.TypeFor[*CFGs](),
FactTypes: []analysis.Fact{new(noReturn)},
Requires: []*analysis.Analyzer{inspect.Analyzer},
}
// noReturn is a fact indicating that a function does not return.
type noReturn struct{}
func (*noReturn) AFact() {}
func (*noReturn) String() string { return "noReturn" }
// A CFGs holds the control-flow graphs
// for all the functions of the current package.
type CFGs struct {
defs map[*ast.Ident]types.Object // from Pass.TypesInfo.Defs
funcDecls map[*types.Func]*declInfo
funcLits map[*ast.FuncLit]*litInfo
noReturn map[*types.Func]bool // functions lacking a reachable return statement
pass *analysis.Pass // transient; nil after construction
}
// NoReturn reports whether the specified control-flow graph cannot return normally.
//
// It is defined for at least all function symbols that appear as the static callee of a
// CallExpr in the current package, even if the callee was imported from a dependency.
//
// The result may incorporate interprocedural information based on induction of
// the "no return" property over the static call graph within the package.
// For example, if f simply calls g and g always calls os.Exit, then both f and g may
// be deemed never to return.
func (c *CFGs) NoReturn(fn *types.Func) bool {
return c.noReturn[fn]
}
// CFGs has two maps: funcDecls for named functions and funcLits for
// unnamed ones. Unlike funcLits, the funcDecls map is not keyed by its
// syntax node, *ast.FuncDecl, because callMayReturn needs to do a
// look-up by *types.Func, and you can get from an *ast.FuncDecl to a
// *types.Func but not the other way.
type declInfo struct {
decl *ast.FuncDecl
cfg *cfg.CFG // iff decl.Body != nil
started bool // to break cycles
}
type litInfo struct {
cfg *cfg.CFG
noReturn bool // (currently unused)
}
// FuncDecl returns the control-flow graph for a named function.
// It returns nil if decl.Body==nil.
func (c *CFGs) FuncDecl(decl *ast.FuncDecl) *cfg.CFG {
if decl.Body == nil {
return nil
}
fn := c.defs[decl.Name].(*types.Func)
return c.funcDecls[fn].cfg
}
// FuncLit returns the control-flow graph for a literal function.
func (c *CFGs) FuncLit(lit *ast.FuncLit) *cfg.CFG {
return c.funcLits[lit].cfg
}
func run(pass *analysis.Pass) (any, error) {
inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
// Because CFG construction consumes and produces noReturn
// facts, CFGs for exported FuncDecls must be built before 'run'
// returns; we cannot construct them lazily.
// (We could build CFGs for FuncLits lazily,
// but the benefit is marginal.)
// Pass 1. Map types.Funcs to ast.FuncDecls in this package.
funcDecls := make(map[*types.Func]*declInfo) // functions and methods
funcLits := make(map[*ast.FuncLit]*litInfo)
var decls []*types.Func // keys(funcDecls), in order
var lits []*ast.FuncLit // keys(funcLits), in order
nodeFilter := []ast.Node{
(*ast.FuncDecl)(nil),
(*ast.FuncLit)(nil),
}
inspect.Preorder(nodeFilter, func(n ast.Node) {
switch n := n.(type) {
case *ast.FuncDecl:
// Type information may be incomplete.
if fn, ok := pass.TypesInfo.Defs[n.Name].(*types.Func); ok {
funcDecls[fn] = &declInfo{decl: n}
decls = append(decls, fn)
}
case *ast.FuncLit:
funcLits[n] = new(litInfo)
lits = append(lits, n)
}
})
c := &CFGs{
defs: pass.TypesInfo.Defs,
funcDecls: funcDecls,
funcLits: funcLits,
noReturn: make(map[*types.Func]bool),
pass: pass,
}
// Pass 2. Build CFGs.
// Build CFGs for named functions.
// Cycles in the static call graph are broken
// arbitrarily but deterministically.
// We create noReturn facts as discovered.
for _, fn := range decls {
c.buildDecl(fn, funcDecls[fn])
}
// Build CFGs for literal functions.
// These aren't relevant to facts (since they aren't named)
// but are required for the CFGs.FuncLit API.
for _, lit := range lits {
li := funcLits[lit]
if li.cfg == nil {
li.cfg = cfg.New(lit.Body, c.callMayReturn)
if li.cfg.NoReturn() {
li.noReturn = true
}
}
}
// All CFGs are now built.
c.pass = nil
return c, nil
}
// di.cfg may be nil on return.
func (c *CFGs) buildDecl(fn *types.Func, di *declInfo) {
// buildDecl may call itself recursively for the same function,
// because cfg.New is passed the callMayReturn method, which
// builds the CFG of the callee, leading to recursion.
// The buildDecl call tree thus resembles the static call graph.
// We mark each node when we start working on it to break cycles.
if di.started {
return // break cycle
}
di.started = true
noreturn, known := knownIntrinsic(fn)
if !known {
if di.decl.Body != nil {
di.cfg = cfg.New(di.decl.Body, c.callMayReturn)
if di.cfg.NoReturn() {
noreturn = true
}
}
}
if noreturn {
c.pass.ExportObjectFact(fn, new(noReturn))
c.noReturn[fn] = true
}
// debugging
if false {
log.Printf("CFG for %s:\n%s (noreturn=%t)\n", fn, di.cfg.Format(c.pass.Fset), noreturn)
}
}
// callMayReturn reports whether the called function may return.
// It is passed to the CFG builder.
func (c *CFGs) callMayReturn(call *ast.CallExpr) (r bool) {
if id, ok := call.Fun.(*ast.Ident); ok && c.pass.TypesInfo.Uses[id] == panicBuiltin {
return false // panic never returns
}
// Is this a static call? Also includes static functions
// parameterized by a type. Such functions may or may not
// return depending on the parameter type, but in some
// cases the answer is definite. We let ctrlflow figure
// that out.
fn := typeutil.StaticCallee(c.pass.TypesInfo, call)
if fn == nil {
return true // callee not statically known; be conservative
}
// Function or method declared in this package?
if di, ok := c.funcDecls[fn]; ok {
c.buildDecl(fn, di)
return !c.noReturn[fn]
}
// Not declared in this package.
// Is there a fact from another package?
if c.pass.ImportObjectFact(fn, new(noReturn)) {
c.noReturn[fn] = true
return false
}
return true
}
var panicBuiltin = types.Universe.Lookup("panic").(*types.Builtin)
// knownIntrinsic reports whether a function intrinsically never
// returns because it stops execution of the calling thread, or does
// in fact return, contrary to its apparent body, because it is
// handled specially by the compiler.
//
// It is the base case in the recursion.
func knownIntrinsic(fn *types.Func) (noreturn, known bool) {
// Add functions here as the need arises, but don't allocate memory.
// Functions known intrinsically never to return.
if typesinternal.IsFunctionNamed(fn, "syscall", "Exit", "ExitProcess", "ExitThread") ||
typesinternal.IsFunctionNamed(fn, "runtime", "Goexit", "fatalthrow", "fatalpanic", "exit") ||
// Following staticcheck (see go/ir/exits.go) we include functions
// in several popular logging packages whose no-return status is
// beyond the analysis to infer.
// TODO(adonovan): make this list extensible.
typesinternal.IsMethodNamed(fn, "go.uber.org/zap", "Logger", "Fatal", "Panic") ||
typesinternal.IsMethodNamed(fn, "go.uber.org/zap", "SugaredLogger", "Fatal", "Fatalw", "Fatalf", "Panic", "Panicw", "Panicf") ||
typesinternal.IsMethodNamed(fn, "github.com/sirupsen/logrus", "Logger", "Exit", "Panic", "Panicf", "Panicln") ||
typesinternal.IsMethodNamed(fn, "github.com/sirupsen/logrus", "Entry", "Panicf", "Panicln") ||
typesinternal.IsFunctionNamed(fn, "k8s.io/klog", "Exit", "ExitDepth", "Exitf", "Exitln", "Fatal", "FatalDepth", "Fatalf", "Fatalln") ||
typesinternal.IsFunctionNamed(fn, "k8s.io/klog/v2", "Exit", "ExitDepth", "Exitf", "Exitln", "Fatal", "FatalDepth", "Fatalf", "Fatalln") {
return true, true
}
// Compiler intrinsics known to return, contrary to
// what analysis of the function body would conclude.
//
// Not all such intrinsics must be listed here: ctrlflow
// considers any function called for its value--such as
// crypto/internal/constanttime.bool2Uint8--to potentially
// return; only functions called as a statement, for effects,
// are no-return candidates.
//
// Unfortunately this does sometimes mean peering into internals.
// Where possible, use the nearest enclosing public API function.
if typesinternal.IsFunctionNamed(fn, "internal/abi", "EscapeNonString") ||
typesinternal.IsFunctionNamed(fn, "hash/maphash", "Comparable") {
return false, true
}
return // unknown
}
================================================
FILE: go/analysis/passes/ctrlflow/ctrlflow_test.go
================================================
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package ctrlflow_test
import (
"go/ast"
"testing"
"golang.org/x/tools/go/analysis/analysistest"
"golang.org/x/tools/go/analysis/passes/ctrlflow"
)
func Test(t *testing.T) {
testdata := analysistest.TestData()
results := analysistest.Run(t, testdata, ctrlflow.Analyzer, "a", "typeparams")
// Perform a minimal smoke test on
// the result (CFG) computed by ctrlflow.
for _, result := range results {
cfgs := result.Result.(*ctrlflow.CFGs)
for _, decl := range result.Action.Package.Syntax[0].Decls {
if decl, ok := decl.(*ast.FuncDecl); ok && decl.Body != nil {
if cfgs.FuncDecl(decl) == nil {
t.Errorf("%s: no CFG for func %s",
result.Action.Package.Fset.Position(decl.Pos()), decl.Name.Name)
}
}
}
}
}
================================================
FILE: go/analysis/passes/ctrlflow/testdata/src/a/a.go
================================================
// Copyright 2021 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package a
// This file tests facts produced by ctrlflow.
import (
"hash/maphash"
"log"
"os"
"runtime"
"syscall"
"testing"
"lib"
)
var cond bool
func a() { // want a:"noReturn"
if cond {
b()
} else {
for {
}
}
}
func b() { // want b:"noReturn"
select {}
}
func f(x int) { // no fact here
switch x {
case 0:
os.Exit(0)
case 1:
panic(0)
}
// default case returns
}
type T int
func (T) method1() { // want method1:"noReturn"
a()
}
func (T) method2() { // (may return)
if cond {
a()
}
}
// Checking for the noreturn fact associated with F ensures that
// ctrlflow proved each of the listed functions was "noReturn".
func standardFunctions(x int) { // want standardFunctions:"noReturn"
t := new(testing.T)
switch x {
case 0:
t.FailNow()
case 1:
t.Fatal()
case 2:
t.Fatalf("")
case 3:
t.Skip()
case 4:
t.SkipNow()
case 5:
t.Skipf("")
case 6:
log.Fatal()
case 7:
log.Fatalf("")
case 8:
log.Fatalln()
case 9:
os.Exit(0)
case 10:
syscall.Exit(0)
case 11:
runtime.Goexit()
case 12:
log.Panic()
case 13:
log.Panicln()
case 14:
log.Panicf("")
default:
panic(0)
}
}
func panicRecover() {
defer func() { recover() }()
panic(nil)
}
func noBody()
func g() {
lib.CanReturn()
}
func h() { // want h:"noReturn"
lib.NoReturn()
}
func returns() {
print(1)
print(2)
print(3)
}
func nobody() // a function with no body is assumed to return
func hasPanic() { // want hasPanic:"noReturn"
print(1)
panic(2)
print(3)
}
func hasSelect() { // want hasSelect:"noReturn"
print(1)
select {}
print(3)
}
func infiniteLoop() { // want infiniteLoop:"noReturn"
print(1)
for {
}
print(3)
}
func ifElse(cond bool) { // want ifElse:"noReturn"
print(1)
if cond {
hasSelect()
} else {
infiniteLoop()
}
print(3)
}
func swtch(x int) { // want swtch:"noReturn"
print(1)
switch x {
case 1:
hasSelect()
case 2:
goexit()
case 3:
logFatal()
case 4:
osexit()
default:
panic(3)
}
}
func _if(cond bool) {
print(1)
if cond {
hasSelect()
}
print(3)
}
func logFatal() { // want logFatal:"noReturn"
print(1)
log.Fatal("oops")
print(2)
}
func testFatal(t *testing.T) { // want testFatal:"noReturn"
print(1)
t.Fatal("oops")
print(2)
}
func goexit() { // want goexit:"noReturn"
print(1)
runtime.Goexit()
print(2)
}
func osexit() { // want osexit:"noReturn"
print(1)
os.Exit(0)
print(2)
}
func intrinsic() { // (no fact)
// Comparable calls abi.EscapeNonString, whose body appears to panic;
// but that's a lie, as EscapeNonString is a compiler intrinsic.
// (go1.24 used a different intrinsic, maphash.escapeForHash.)
maphash.Comparable[int](maphash.Seed{}, 0)
}
================================================
FILE: go/analysis/passes/ctrlflow/testdata/src/lib/lib.go
================================================
// Copyright 2021 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package lib
func CanReturn() {}
func NoReturn() {
for {
}
}
================================================
FILE: go/analysis/passes/ctrlflow/testdata/src/typeparams/typeparams.go
================================================
// Copyright 2021 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package a
// This file tests facts produced by ctrlflow.
var cond bool
var funcs = []func(){func() {}}
func a[A any]() { // want a:"noReturn"
if cond {
funcs[0]()
b[A]()
} else {
for {
}
}
}
func b[B any]() { // want b:"noReturn"
select {}
}
func c[A, B any]() { // want c:"noReturn"
if cond {
a[A]()
} else {
d[A, B]()
}
}
func d[A, B any]() { // want d:"noReturn"
b[B]()
}
type I[T any] interface {
Id(T) T
}
func e[T any](i I[T], t T) T {
return i.Id(t)
}
func k[T any](i I[T], t T) T { // want k:"noReturn"
b[T]()
return i.Id(t)
}
type T[X any] int
func (T[X]) method1() { // want method1:"noReturn"
a[X]()
}
func (T[X]) method2() { // (may return)
if cond {
a[X]()
} else {
funcs[0]()
}
}
================================================
FILE: go/analysis/passes/deepequalerrors/deepequalerrors.go
================================================
// Copyright 2019 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package deepequalerrors defines an Analyzer that checks for the use
// of reflect.DeepEqual with error values.
package deepequalerrors
import (
"go/ast"
"go/types"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/ast/inspector"
"golang.org/x/tools/go/types/typeutil"
"golang.org/x/tools/internal/typesinternal"
)
const Doc = `check for calls of reflect.DeepEqual on error values
The deepequalerrors checker looks for calls of the form:
reflect.DeepEqual(err1, err2)
where err1 and err2 are errors. Using reflect.DeepEqual to compare
errors is discouraged.`
var Analyzer = &analysis.Analyzer{
Name: "deepequalerrors",
Doc: Doc,
URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/deepequalerrors",
Requires: []*analysis.Analyzer{inspect.Analyzer},
Run: run,
}
func run(pass *analysis.Pass) (any, error) {
if !typesinternal.Imports(pass.Pkg, "reflect") {
return nil, nil // doesn't directly import reflect
}
inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
nodeFilter := []ast.Node{
(*ast.CallExpr)(nil),
}
inspect.Preorder(nodeFilter, func(n ast.Node) {
call := n.(*ast.CallExpr)
obj := typeutil.Callee(pass.TypesInfo, call)
if typesinternal.IsFunctionNamed(obj, "reflect", "DeepEqual") && hasError(pass, call.Args[0]) && hasError(pass, call.Args[1]) {
pass.ReportRangef(call, "avoid using reflect.DeepEqual with errors")
}
})
return nil, nil
}
var errorType = types.Universe.Lookup("error").Type().Underlying().(*types.Interface)
// hasError reports whether the type of e contains the type error.
// See containsError, below, for the meaning of "contains".
func hasError(pass *analysis.Pass, e ast.Expr) bool {
tv, ok := pass.TypesInfo.Types[e]
if !ok { // no type info, assume good
return false
}
return containsError(tv.Type)
}
// Report whether any type that typ could store and that could be compared is the
// error type. This includes typ itself, as well as the types of struct field, slice
// and array elements, map keys and elements, and pointers. It does not include
// channel types (incomparable), arg and result types of a Signature (not stored), or
// methods of a named or interface type (not stored).
func containsError(typ types.Type) bool {
// Track types being processed, to avoid infinite recursion.
// Using types as keys here is OK because we are checking for the identical pointer, not
// type identity. See analysis/passes/printf/types.go.
inProgress := make(map[types.Type]bool)
var check func(t types.Type) bool
check = func(t types.Type) bool {
if t == errorType {
return true
}
if inProgress[t] {
return false
}
inProgress[t] = true
switch t := t.(type) {
case *types.Pointer:
return check(t.Elem())
case *types.Slice:
return check(t.Elem())
case *types.Array:
return check(t.Elem())
case *types.Map:
return check(t.Key()) || check(t.Elem())
case *types.Struct:
for field := range t.Fields() {
if check(field.Type()) {
return true
}
}
case *types.Named, *types.Alias:
return check(t.Underlying())
// We list the remaining valid type kinds for completeness.
case *types.Basic:
case *types.Chan: // channels store values, but they are not comparable
case *types.Signature:
case *types.Tuple: // tuples are only part of signatures
case *types.Interface:
}
return false
}
return check(typ)
}
================================================
FILE: go/analysis/passes/deepequalerrors/deepequalerrors_test.go
================================================
// Copyright 2019 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package deepequalerrors_test
import (
"testing"
"golang.org/x/tools/go/analysis/analysistest"
"golang.org/x/tools/go/analysis/passes/deepequalerrors"
)
func Test(t *testing.T) {
testdata := analysistest.TestData()
analysistest.Run(t, testdata, deepequalerrors.Analyzer, "a", "typeparams")
}
================================================
FILE: go/analysis/passes/deepequalerrors/testdata/src/a/a.go
================================================
// Copyright 2019 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// This file contains tests for the deepequalerrors checker.
package a
import (
"io"
"os"
"reflect"
)
type myError int
func (myError) Error() string { return "" }
func bad() error { return nil }
type s1 struct {
s2 *s2
i int
}
type myError2 error
type s2 struct {
s1 *s1
errs []*myError2
}
func hasError() {
var e error
var m myError2
reflect.DeepEqual(bad(), e) // want `avoid using reflect.DeepEqual with errors`
reflect.DeepEqual(io.EOF, io.EOF) // want `avoid using reflect.DeepEqual with errors`
reflect.DeepEqual(e, &e) // want `avoid using reflect.DeepEqual with errors`
reflect.DeepEqual(e, m) // want `avoid using reflect.DeepEqual with errors`
reflect.DeepEqual(e, s1{}) // want `avoid using reflect.DeepEqual with errors`
reflect.DeepEqual(e, [1]error{}) // want `avoid using reflect.DeepEqual with errors`
reflect.DeepEqual(e, map[error]int{}) // want `avoid using reflect.DeepEqual with errors`
reflect.DeepEqual(e, map[int]error{}) // want `avoid using reflect.DeepEqual with errors`
// We catch the next not because *os.PathError implements error, but because it contains
// a field Err of type error.
reflect.DeepEqual(&os.PathError{}, io.EOF) // want `avoid using reflect.DeepEqual with errors`
}
func notHasError() {
reflect.ValueOf(4) // not reflect.DeepEqual
reflect.DeepEqual(3, 4) // not errors
reflect.DeepEqual(5, io.EOF) // only one error
reflect.DeepEqual(myError(1), io.EOF) // not types that implement error
}
================================================
FILE: go/analysis/passes/deepequalerrors/testdata/src/typeparams/typeparams.go
================================================
// Copyright 2021 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// This file contains tests for the deepequalerrors checker.
package a
import (
"io"
"os"
"reflect"
)
type myError int
func (myError) Error() string { return "" }
func bad[T any]() T {
var t T
return t
}
type s1 struct {
s2 *s2[myError2]
i int
}
type myError2 error
type s2[T any] struct {
s1 *s1
errs []*T
}
func hasError() {
var e error
var m myError2
reflect.DeepEqual(bad[error](), e) // want `avoid using reflect.DeepEqual with errors`
reflect.DeepEqual(io.EOF, io.EOF) // want `avoid using reflect.DeepEqual with errors`
reflect.DeepEqual(e, &e) // want `avoid using reflect.DeepEqual with errors`
reflect.DeepEqual(e, m) // want `avoid using reflect.DeepEqual with errors`
reflect.DeepEqual(e, s1{}) // want `avoid using reflect.DeepEqual with errors`
reflect.DeepEqual(e, [1]error{}) // want `avoid using reflect.DeepEqual with errors`
reflect.DeepEqual(e, map[error]int{}) // want `avoid using reflect.DeepEqual with errors`
reflect.DeepEqual(e, map[int]error{}) // want `avoid using reflect.DeepEqual with errors`
// We catch the next not because *os.PathError implements error, but because it contains
// a field Err of type error.
reflect.DeepEqual(&os.PathError{}, io.EOF) // want `avoid using reflect.DeepEqual with errors`
}
func notHasError() {
reflect.ValueOf(4) // not reflect.DeepEqual
reflect.DeepEqual(3, 4) // not errors
reflect.DeepEqual(5, io.EOF) // only one error
reflect.DeepEqual(myError(1), io.EOF) // not types that implement error
}
================================================
FILE: go/analysis/passes/defers/cmd/defers/main.go
================================================
// Copyright 2023 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// The defers command runs the defers analyzer.
package main
import (
"golang.org/x/tools/go/analysis/passes/defers"
"golang.org/x/tools/go/analysis/singlechecker"
)
func main() { singlechecker.Main(defers.Analyzer) }
================================================
FILE: go/analysis/passes/defers/defers.go
================================================
// Copyright 2023 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package defers
import (
_ "embed"
"go/ast"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/ast/inspector"
"golang.org/x/tools/go/types/typeutil"
"golang.org/x/tools/internal/analysis/analyzerutil"
"golang.org/x/tools/internal/typesinternal"
)
//go:embed doc.go
var doc string
// Analyzer is the defers analyzer.
var Analyzer = &analysis.Analyzer{
Name: "defers",
Requires: []*analysis.Analyzer{inspect.Analyzer},
Doc: analyzerutil.MustExtractDoc(doc, "defers"),
URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/defers",
Run: run,
}
func run(pass *analysis.Pass) (any, error) {
if !typesinternal.Imports(pass.Pkg, "time") {
return nil, nil
}
checkDeferCall := func(node ast.Node) bool {
switch v := node.(type) {
case *ast.CallExpr:
if typesinternal.IsFunctionNamed(typeutil.Callee(pass.TypesInfo, v), "time", "Since") {
pass.Reportf(v.Pos(), "call to time.Since is not deferred")
}
case *ast.FuncLit:
return false // prune
}
return true
}
inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
nodeFilter := []ast.Node{
(*ast.DeferStmt)(nil),
}
inspect.Preorder(nodeFilter, func(n ast.Node) {
d := n.(*ast.DeferStmt)
ast.Inspect(d.Call, checkDeferCall)
})
return nil, nil
}
================================================
FILE: go/analysis/passes/defers/defers_test.go
================================================
// Copyright 2023 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package defers_test
import (
"testing"
"golang.org/x/tools/go/analysis/analysistest"
"golang.org/x/tools/go/analysis/passes/defers"
)
func Test(t *testing.T) {
testdata := analysistest.TestData()
analysistest.Run(t, testdata, defers.Analyzer, "a")
}
================================================
FILE: go/analysis/passes/defers/doc.go
================================================
// Copyright 2023 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package defers defines an Analyzer that checks for common mistakes in defer
// statements.
//
// # Analyzer defers
//
// defers: report common mistakes in defer statements
//
// The defers analyzer reports a diagnostic when a defer statement would
// result in a non-deferred call to time.Since, as experience has shown
// that this is nearly always a mistake.
//
// For example:
//
// start := time.Now()
// ...
// defer recordLatency(time.Since(start)) // error: call to time.Since is not deferred
//
// The correct code is:
//
// defer func() { recordLatency(time.Since(start)) }()
package defers
================================================
FILE: go/analysis/passes/defers/testdata/src/a/a.go
================================================
// Copyright 2023 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package a
import (
"fmt"
"time"
)
func Since() (t time.Duration) {
return
}
func x(time.Duration) {}
func x2(float64) {}
func good() {
// The following are OK because func is not evaluated in defer invocation.
now := time.Now()
defer func() {
fmt.Println(time.Since(now)) // OK because time.Since is not evaluated in defer
}()
evalBefore := time.Since(now)
defer fmt.Println(evalBefore)
do := func(f func()) {}
defer do(func() { time.Since(now) })
defer fmt.Println(Since()) // OK because Since function is not in module time
defer copy([]int(nil), []int{1}) // check that a builtin doesn't cause a panic
}
type y struct{}
func (y) A(float64) {}
func (*y) B(float64) {}
func (y) C(time.Duration) {}
func (*y) D(time.Duration) {}
func bad() {
var zero time.Time
now := time.Now()
defer time.Since(zero) // want "call to time.Since is not deferred"
defer time.Since(now) // want "call to time.Since is not deferred"
defer fmt.Println(time.Since(now)) // want "call to time.Since is not deferred"
defer fmt.Println(time.Since(time.Now())) // want "call to time.Since is not deferred"
defer x(time.Since(now)) // want "call to time.Since is not deferred"
defer x2(time.Since(now).Seconds()) // want "call to time.Since is not deferred"
defer y{}.A(time.Since(now).Seconds()) // want "call to time.Since is not deferred"
defer (&y{}).B(time.Since(now).Seconds()) // want "call to time.Since is not deferred"
defer y{}.C(time.Since(now)) // want "call to time.Since is not deferred"
defer (&y{}).D(time.Since(now)) // want "call to time.Since is not deferred"
}
func ugly() {
// The following is ok even though time.Since is evaluated. We don't
// walk into function literals or check what function definitions are doing.
defer x((func() time.Duration { return time.Since(time.Now()) })())
}
================================================
FILE: go/analysis/passes/directive/directive.go
================================================
// Copyright 2023 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package directive defines an Analyzer that checks known Go toolchain directives.
package directive
import (
"go/ast"
"go/parser"
"go/token"
"strings"
"unicode"
"unicode/utf8"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/internal/analysis/analyzerutil"
)
const Doc = `check Go toolchain directives such as //go:debug
This analyzer checks for problems with known Go toolchain directives
in all Go source files in a package directory, even those excluded by
//go:build constraints, and all non-Go source files too.
For //go:debug (see https://go.dev/doc/godebug), the analyzer checks
that the directives are placed only in Go source files, only above the
package comment, and only in package main or *_test.go files.
Support for other known directives may be added in the future.
This analyzer does not check //go:build, which is handled by the
buildtag analyzer.
`
var Analyzer = &analysis.Analyzer{
Name: "directive",
Doc: Doc,
URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/directive",
Run: runDirective,
}
func runDirective(pass *analysis.Pass) (any, error) {
for _, f := range pass.Files {
checkGoFile(pass, f)
}
for _, name := range pass.OtherFiles {
if err := checkOtherFile(pass, name); err != nil {
return nil, err
}
}
for _, name := range pass.IgnoredFiles {
if strings.HasSuffix(name, ".go") {
f, err := parser.ParseFile(pass.Fset, name, nil, parser.ParseComments)
if err != nil {
// Not valid Go source code - not our job to diagnose, so ignore.
continue
}
checkGoFile(pass, f)
} else {
if err := checkOtherFile(pass, name); err != nil {
return nil, err
}
}
}
return nil, nil
}
func checkGoFile(pass *analysis.Pass, f *ast.File) {
check := newChecker(pass, pass.Fset.File(f.Package).Name(), f)
for _, group := range f.Comments {
// A //go:build or a //go:debug comment is ignored after the package declaration
// (but adjoining it is OK, in contrast to +build comments).
if group.Pos() >= f.Package {
check.inHeader = false
}
// Check each line of a //-comment.
for _, c := range group.List {
check.comment(c.Slash, c.Text)
}
}
}
func checkOtherFile(pass *analysis.Pass, filename string) error {
// We cannot use the Go parser, since is not a Go source file.
// Read the raw bytes instead.
content, tf, err := analyzerutil.ReadFile(pass, filename)
if err != nil {
return err
}
check := newChecker(pass, filename, nil)
check.nonGoFile(token.Pos(tf.Base()), string(content))
return nil
}
type checker struct {
pass *analysis.Pass
filename string
file *ast.File // nil for non-Go file
inHeader bool // in file header (before or adjoining package declaration)
}
func newChecker(pass *analysis.Pass, filename string, file *ast.File) *checker {
return &checker{
pass: pass,
filename: filename,
file: file,
inHeader: true,
}
}
func (check *checker) nonGoFile(pos token.Pos, fullText string) {
// Process each line.
text := fullText
inStar := false
for text != "" {
offset := len(fullText) - len(text)
var line string
line, text, _ = strings.Cut(text, "\n")
if !inStar && strings.HasPrefix(line, "//") {
check.comment(pos+token.Pos(offset), line)
continue
}
// Skip over, cut out any /* */ comments,
// to avoid being confused by a commented-out // comment.
for {
line = strings.TrimSpace(line)
if inStar {
var ok bool
_, line, ok = strings.Cut(line, "*/")
if !ok {
break
}
inStar = false
continue
}
line, inStar = stringsCutPrefix(line, "/*")
if !inStar {
break
}
}
if line != "" {
// Found non-comment non-blank line.
// Ends space for valid //go:build comments,
// but also ends the fraction of the file we can
// reliably parse. From this point on we might
// incorrectly flag "comments" inside multiline
// string constants or anything else (this might
// not even be a Go program). So stop.
break
}
}
}
func (check *checker) comment(pos token.Pos, line string) {
if !strings.HasPrefix(line, "//go:") {
return
}
// testing hack: stop at // ERROR
if i := strings.Index(line, " // ERROR "); i >= 0 {
line = line[:i]
}
verb := line
if i := strings.IndexFunc(verb, unicode.IsSpace); i >= 0 {
verb = verb[:i]
if line[i] != ' ' && line[i] != '\t' && line[i] != '\n' {
r, _ := utf8.DecodeRuneInString(line[i:])
check.pass.Reportf(pos, "invalid space %#q in %s directive", r, verb)
}
}
switch verb {
default:
// TODO: Use the go language version for the file.
// If that version is not newer than us, then we can
// report unknown directives.
case "//go:build":
// Ignore. The buildtag analyzer reports misplaced comments.
case "//go:debug":
if check.file == nil {
check.pass.Reportf(pos, "//go:debug directive only valid in Go source files")
} else if check.file.Name.Name != "main" && !strings.HasSuffix(check.filename, "_test.go") {
check.pass.Reportf(pos, "//go:debug directive only valid in package main or test")
} else if !check.inHeader {
check.pass.Reportf(pos, "//go:debug directive only valid before package declaration")
}
}
}
// Go 1.20 strings.CutPrefix.
func stringsCutPrefix(s, prefix string) (after string, found bool) {
if !strings.HasPrefix(s, prefix) {
return s, false
}
return s[len(prefix):], true
}
================================================
FILE: go/analysis/passes/directive/directive_test.go
================================================
// Copyright 2023 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package directive_test
import (
"testing"
"golang.org/x/tools/go/analysis/analysistest"
"golang.org/x/tools/go/analysis/passes/directive"
)
func Test(t *testing.T) {
// This test has a dedicated hack in the analysistest package:
// Because it cares about IgnoredFiles, which most analyzers
// ignore, the test framework will consider expectations in
// ignore files too, but only for this analyzer.
analysistest.Run(t, analysistest.TestData(), directive.Analyzer, "a")
}
================================================
FILE: go/analysis/passes/directive/testdata/src/a/badspace.go
================================================
// Copyright 2023 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build ignore
// want +1 `invalid space '\\u00a0' in //go:debug directive`
//go:debug 00a0
package main
================================================
FILE: go/analysis/passes/directive/testdata/src/a/issue66046.go
================================================
// Copyright 2024 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build ignore
//go:debug panicnil=1
package main
================================================
FILE: go/analysis/passes/directive/testdata/src/a/misplaced.go
================================================
// Copyright 2023 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build ignore
package main
// want +1 `//go:debug directive only valid before package declaration`
//go:debug panicnil=1
================================================
FILE: go/analysis/passes/directive/testdata/src/a/misplaced.s
================================================
// Copyright 2023 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// want +1 `//go:debug directive only valid in Go source files`
//go:debug panicnil=1
/*
can skip over comments
//go:debug doesn't matter here
*/
// want +1 `//go:debug directive only valid in Go source files`
//go:debug panicnil=1
package a
// no error here because we can't parse this far
//go:debug panicnil=1
================================================
FILE: go/analysis/passes/directive/testdata/src/a/misplaced_test.go
================================================
// Copyright 2023 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:debug panicnil=1
package p_test
// want +1 `//go:debug directive only valid before package declaration`
//go:debug panicnil=1
================================================
FILE: go/analysis/passes/directive/testdata/src/a/p.go
================================================
// Copyright 2023 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// want +1 `//go:debug directive only valid in package main or test`
//go:debug panicnil=1
package p
// want +1 `//go:debug directive only valid in package main or test`
//go:debug panicnil=1
================================================
FILE: go/analysis/passes/errorsas/errorsas.go
================================================
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// The errorsas package defines an Analyzer that checks that the second argument to
// errors.As is a pointer to a type implementing error.
package errorsas
import (
"errors"
"go/ast"
"go/types"
"golang.org/x/tools/go/analysis"
typeindexanalyzer "golang.org/x/tools/internal/analysis/typeindex"
"golang.org/x/tools/internal/typesinternal/typeindex"
)
const Doc = `report passing non-pointer or non-error values to errors.As
The errorsas analyzer reports calls to errors.As where the type
of the second argument is not a pointer to a type implementing error.`
var Analyzer = &analysis.Analyzer{
Name: "errorsas",
Doc: Doc,
URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/errorsas",
Requires: []*analysis.Analyzer{typeindexanalyzer.Analyzer},
Run: run,
}
func run(pass *analysis.Pass) (any, error) {
switch pass.Pkg.Path() {
case "errors", "errors_test":
// These packages know how to use their own APIs.
// Sometimes they are testing what happens to incorrect programs.
return nil, nil
}
var (
index = pass.ResultOf[typeindexanalyzer.Analyzer].(*typeindex.Index)
info = pass.TypesInfo
)
for curCall := range index.Calls(index.Object("errors", "As")) {
call := curCall.Node().(*ast.CallExpr)
if len(call.Args) < 2 {
continue // spread call: errors.As(pair())
}
// Check for incorrect arguments.
if err := checkAsTarget(info, call.Args[1]); err != nil {
pass.ReportRangef(call, "%v", err)
continue
}
}
return nil, nil
}
// checkAsTarget reports an error if the second argument to errors.As is invalid.
func checkAsTarget(info *types.Info, e ast.Expr) error {
t := info.Types[e].Type
if types.Identical(t.Underlying(), anyType) {
// A target of any is always allowed, since it often indicates
// a value forwarded from another source.
return nil
}
pt, ok := t.Underlying().(*types.Pointer)
if !ok {
return errors.New("second argument to errors.As must be a non-nil pointer to either a type that implements error, or to any interface type")
}
if types.Identical(pt.Elem(), errorType) {
return errors.New("second argument to errors.As should not be *error")
}
if !types.IsInterface(pt.Elem()) && !types.AssignableTo(pt.Elem(), errorType) {
return errors.New("second argument to errors.As must be a non-nil pointer to either a type that implements error, or to any interface type")
}
return nil
}
var (
anyType = types.Universe.Lookup("any").Type()
errorType = types.Universe.Lookup("error").Type()
)
================================================
FILE: go/analysis/passes/errorsas/errorsas_test.go
================================================
// Copyright 2019 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package errorsas_test
import (
"testing"
"golang.org/x/tools/go/analysis/analysistest"
"golang.org/x/tools/go/analysis/passes/errorsas"
)
func Test(t *testing.T) {
testdata := analysistest.TestData()
analysistest.Run(t, testdata, errorsas.Analyzer, "a", "typeparams")
}
================================================
FILE: go/analysis/passes/errorsas/main.go
================================================
// Copyright 2025 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build ignore
// The errorsas command applies the golang.org/x/tools/go/analysis/passes/errorsas
// analysis to the specified packages of Go source code.
package main
import (
"golang.org/x/tools/go/analysis/passes/errorsas"
"golang.org/x/tools/go/analysis/singlechecker"
)
func main() { singlechecker.Main(errorsas.Analyzer) }
================================================
FILE: go/analysis/passes/errorsas/testdata/src/a/a.go
================================================
// Copyright 2019 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// This file contains tests for the errorsas checker.
package a
import "errors"
type myError int
func (myError) Error() string { return "" }
func perr() *error { return nil }
type iface interface {
m()
}
func two() (error, interface{}) { return nil, nil }
func _() {
var (
e error
m myError
i int
f iface
ei interface{}
)
errors.As(nil, &e) // want `second argument to errors.As should not be \*error`
errors.As(nil, &m) // *T where T implements error
errors.As(nil, &f) // *interface
errors.As(nil, perr()) // want `second argument to errors.As should not be \*error`
errors.As(nil, ei) // empty interface
errors.As(nil, nil) // want `second argument to errors.As must be a non-nil pointer to either a type that implements error, or to any interface type`
errors.As(nil, e) // want `second argument to errors.As must be a non-nil pointer to either a type that implements error, or to any interface type`
errors.As(nil, m) // want `second argument to errors.As must be a non-nil pointer to either a type that implements error, or to any interface type`
errors.As(nil, f) // want `second argument to errors.As must be a non-nil pointer to either a type that implements error, or to any interface type`
errors.As(nil, &i) // want `second argument to errors.As must be a non-nil pointer to either a type that implements error, or to any interface type`
errors.As(two())
}
================================================
FILE: go/analysis/passes/errorsas/testdata/src/typeparams/typeparams.go
================================================
// Copyright 2019 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// This file contains tests for the errorsas checker.
package a
import "errors"
type myError[T any] struct{ t T }
func (myError[T]) Error() string { return "" }
type twice[T any] struct {
t T
}
func perr[T any]() *T { return nil }
func two[T any]() (error, *T) { return nil, nil }
func _[E error](e E) {
var (
m myError[int]
tw twice[myError[int]]
)
errors.As(nil, &e)
errors.As(nil, &m) // *T where T implements error
errors.As(nil, &tw.t) // *T where T implements error
errors.As(nil, perr[error]()) // want `second argument to errors.As should not be \*error`
errors.As(nil, e) // want `second argument to errors.As must be a non-nil pointer to either a type that implements error, or to any interface type`
errors.As(nil, m) // want `second argument to errors.As must be a non-nil pointer to either a type that implements error, or to any interface type`
errors.As(nil, tw.t) // want `second argument to errors.As must be a non-nil pointer to either a type that implements error, or to any interface type`
errors.As(two[error]())
}
================================================
FILE: go/analysis/passes/fieldalignment/cmd/fieldalignment/main.go
================================================
// Copyright 2021 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package main
import (
"golang.org/x/tools/go/analysis/passes/fieldalignment"
"golang.org/x/tools/go/analysis/singlechecker"
)
func main() { singlechecker.Main(fieldalignment.Analyzer) }
================================================
FILE: go/analysis/passes/fieldalignment/fieldalignment.go
================================================
// Copyright 2020 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package fieldalignment defines an Analyzer that detects structs that would use less
// memory if their fields were sorted.
package fieldalignment
import (
"bytes"
"fmt"
"go/ast"
"go/format"
"go/token"
"go/types"
"sort"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/ast/inspector"
"golang.org/x/tools/internal/astutil"
)
const Doc = `find structs that would use less memory if their fields were sorted
This analyzer finds structs that can be rearranged to use less memory, and provides
a suggested edit with the most compact order.
Note that there are two different diagnostics reported. One checks struct size,
and the other reports "pointer bytes" used. Pointer bytes is how many bytes of the
object that the garbage collector has to potentially scan for pointers, for example:
struct { uint32; string }
have 16 pointer bytes because the garbage collector has to scan up through the string's
inner pointer.
struct { string; *uint32 }
has 24 pointer bytes because it has to scan further through the *uint32.
struct { string; uint32 }
has 8 because it can stop immediately after the string pointer.
Be aware that the most compact order is not always the most efficient.
In rare cases it may cause two variables each updated by its own goroutine
to occupy the same CPU cache line, inducing a form of memory contention
known as "false sharing" that slows down both goroutines.
Unlike most analyzers, which report likely mistakes, the diagnostics
produced by fieldanalyzer very rarely indicate a significant problem,
so the analyzer is not included in typical suites such as vet or
gopls. Use this standalone command to run it on your code:
$ go install golang.org/x/tools/go/analysis/passes/fieldalignment/cmd/fieldalignment@latest
$ fieldalignment [packages]
`
var Analyzer = &analysis.Analyzer{
Name: "fieldalignment",
Doc: Doc,
URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/fieldalignment",
Requires: []*analysis.Analyzer{inspect.Analyzer},
Run: run,
}
func run(pass *analysis.Pass) (any, error) {
inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
nodeFilter := []ast.Node{
(*ast.StructType)(nil),
}
inspect.Preorder(nodeFilter, func(node ast.Node) {
var s *ast.StructType
var ok bool
if s, ok = node.(*ast.StructType); !ok {
return
}
if tv, ok := pass.TypesInfo.Types[s]; ok {
fieldalignment(pass, s, tv.Type.(*types.Struct))
}
})
return nil, nil
}
var unsafePointerTyp = types.Unsafe.Scope().Lookup("Pointer").(*types.TypeName).Type()
func fieldalignment(pass *analysis.Pass, node *ast.StructType, typ *types.Struct) {
wordSize := pass.TypesSizes.Sizeof(unsafePointerTyp)
maxAlign := pass.TypesSizes.Alignof(unsafePointerTyp)
s := gcSizes{wordSize, maxAlign}
optimal, indexes := optimalOrder(typ, &s)
optsz, optptrs := s.Sizeof(optimal), s.ptrdata(optimal)
var message string
if sz := s.Sizeof(typ); sz != optsz {
message = fmt.Sprintf("struct of size %d could be %d", sz, optsz)
} else if ptrs := s.ptrdata(typ); ptrs != optptrs {
message = fmt.Sprintf("struct with %d pointer bytes could be %d", ptrs, optptrs)
} else {
// Already optimal order.
return
}
// Analyzers borrow syntax tree; they do not own them and must modify them.
// This Clone operation is a quick fix to the data race introduced
// in CL 278872 by the clearing of the Comment and Doc fields below.
node = astutil.CloneNode(node)
// Flatten the ast node since it could have multiple field names per list item while
// *types.Struct only have one item per field.
// TODO: Preserve multi-named fields instead of flattening.
var flat []*ast.Field
for _, f := range node.Fields.List {
// TODO: Preserve comment, for now get rid of them.
// See https://github.com/golang/go/issues/20744
f.Comment = nil
f.Doc = nil
if len(f.Names) <= 1 {
flat = append(flat, f)
continue
}
for _, name := range f.Names {
flat = append(flat, &ast.Field{
Names: []*ast.Ident{name},
Type: f.Type,
})
}
}
// Sort fields according to the optimal order.
var reordered []*ast.Field
for _, index := range indexes {
reordered = append(reordered, flat[index])
}
newStr := &ast.StructType{
Fields: &ast.FieldList{
List: reordered,
},
}
// Write the newly aligned struct node to get the content for suggested fixes.
var buf bytes.Buffer
if err := format.Node(&buf, token.NewFileSet(), newStr); err != nil {
return
}
pass.Report(analysis.Diagnostic{
Pos: node.Pos(),
End: node.Pos() + token.Pos(len("struct")),
Message: message,
SuggestedFixes: []analysis.SuggestedFix{{
Message: "Rearrange fields",
TextEdits: []analysis.TextEdit{{
Pos: node.Pos(),
End: node.End(),
NewText: buf.Bytes(),
}},
}},
})
}
func optimalOrder(str *types.Struct, sizes *gcSizes) (*types.Struct, []int) {
nf := str.NumFields()
type elem struct {
index int
alignof int64
sizeof int64
ptrdata int64
}
elems := make([]elem, nf)
for i := range nf {
field := str.Field(i)
ft := field.Type()
elems[i] = elem{
i,
sizes.Alignof(ft),
sizes.Sizeof(ft),
sizes.ptrdata(ft),
}
}
sort.Slice(elems, func(i, j int) bool {
ei := &elems[i]
ej := &elems[j]
// Place zero sized objects before non-zero sized objects.
zeroi := ei.sizeof == 0
zeroj := ej.sizeof == 0
if zeroi != zeroj {
return zeroi
}
// Next, place more tightly aligned objects before less tightly aligned objects.
if ei.alignof != ej.alignof {
return ei.alignof > ej.alignof
}
// Place pointerful objects before pointer-free objects.
noptrsi := ei.ptrdata == 0
noptrsj := ej.ptrdata == 0
if noptrsi != noptrsj {
return noptrsj
}
if !noptrsi {
// If both have pointers...
// ... then place objects with less trailing
// non-pointer bytes earlier. That is, place
// the field with the most trailing
// non-pointer bytes at the end of the
// pointerful section.
traili := ei.sizeof - ei.ptrdata
trailj := ej.sizeof - ej.ptrdata
if traili != trailj {
return traili < trailj
}
}
// Lastly, order by size.
if ei.sizeof != ej.sizeof {
return ei.sizeof > ej.sizeof
}
return false
})
fields := make([]*types.Var, nf)
indexes := make([]int, nf)
for i, e := range elems {
fields[i] = str.Field(e.index)
indexes[i] = e.index
}
return types.NewStruct(fields, nil), indexes
}
// Code below based on go/types.StdSizes.
type gcSizes struct {
WordSize int64
MaxAlign int64
}
func (s *gcSizes) Alignof(T types.Type) int64 {
// For arrays and structs, alignment is defined in terms
// of alignment of the elements and fields, respectively.
switch t := T.Underlying().(type) {
case *types.Array:
// spec: "For a variable x of array type: unsafe.Alignof(x)
// is the same as unsafe.Alignof(x[0]), but at least 1."
return s.Alignof(t.Elem())
case *types.Struct:
// spec: "For a variable x of struct type: unsafe.Alignof(x)
// is the largest of the values unsafe.Alignof(x.f) for each
// field f of x, but at least 1."
max := int64(1)
for i, nf := 0, t.NumFields(); i < nf; i++ {
if a := s.Alignof(t.Field(i).Type()); a > max {
max = a
}
}
return max
}
a := s.Sizeof(T) // may be 0
// spec: "For a variable x of any type: unsafe.Alignof(x) is at least 1."
if a < 1 {
return 1
}
if a > s.MaxAlign {
return s.MaxAlign
}
return a
}
var basicSizes = [...]byte{
types.Bool: 1,
types.Int8: 1,
types.Int16: 2,
types.Int32: 4,
types.Int64: 8,
types.Uint8: 1,
types.Uint16: 2,
types.Uint32: 4,
types.Uint64: 8,
types.Float32: 4,
types.Float64: 8,
types.Complex64: 8,
types.Complex128: 16,
}
func (s *gcSizes) Sizeof(T types.Type) int64 {
switch t := T.Underlying().(type) {
case *types.Basic:
k := t.Kind()
if int(k) < len(basicSizes) {
if s := basicSizes[k]; s > 0 {
return int64(s)
}
}
if k == types.String {
return s.WordSize * 2
}
case *types.Array:
return t.Len() * s.Sizeof(t.Elem())
case *types.Slice:
return s.WordSize * 3
case *types.Struct:
nf := t.NumFields()
if nf == 0 {
return 0
}
var o int64
max := int64(1)
for i := range nf {
ft := t.Field(i).Type()
a, sz := s.Alignof(ft), s.Sizeof(ft)
if a > max {
max = a
}
if i == nf-1 && sz == 0 && o != 0 {
sz = 1
}
o = align(o, a) + sz
}
return align(o, max)
case *types.Interface:
return s.WordSize * 2
}
return s.WordSize // catch-all
}
// align returns the smallest y >= x such that y % a == 0.
func align(x, a int64) int64 {
y := x + a - 1
return y - y%a
}
func (s *gcSizes) ptrdata(T types.Type) int64 {
switch t := T.Underlying().(type) {
case *types.Basic:
switch t.Kind() {
case types.String, types.UnsafePointer:
return s.WordSize
}
return 0
case *types.Chan, *types.Map, *types.Pointer, *types.Signature, *types.Slice:
return s.WordSize
case *types.Interface:
return 2 * s.WordSize
case *types.Array:
n := t.Len()
if n == 0 {
return 0
}
a := s.ptrdata(t.Elem())
if a == 0 {
return 0
}
z := s.Sizeof(t.Elem())
return (n-1)*z + a
case *types.Struct:
nf := t.NumFields()
if nf == 0 {
return 0
}
var o, p int64
for i := range nf {
ft := t.Field(i).Type()
a, sz := s.Alignof(ft), s.Sizeof(ft)
fp := s.ptrdata(ft)
o = align(o, a)
if fp != 0 {
p = o + fp
}
o += sz
}
return p
}
panic("impossible")
}
================================================
FILE: go/analysis/passes/fieldalignment/fieldalignment_test.go
================================================
// Copyright 2020 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package fieldalignment_test
import (
"testing"
"golang.org/x/tools/go/analysis/analysistest"
"golang.org/x/tools/go/analysis/passes/fieldalignment"
)
func TestTest(t *testing.T) {
testdata := analysistest.TestData()
analysistest.RunWithSuggestedFixes(t, testdata, fieldalignment.Analyzer, "a")
}
================================================
FILE: go/analysis/passes/fieldalignment/testdata/src/a/a.go
================================================
package a
type Good struct {
y int32
x byte
z byte
}
type Bad struct { // want "struct of size 12 could be 8"
x byte
y int32
z byte
}
type ZeroGood struct {
a [0]byte
b uint32
}
type ZeroBad struct { // want "struct of size 8 could be 4"
a uint32
b [0]byte
}
type NoNameGood struct {
Good
y int32
x byte
z byte
}
type NoNameBad struct { // want "struct of size 20 could be 16"
Good
x byte
y int32
z byte
}
type WithComments struct { // want "struct of size 8 could be 4"
// doc style comment
a uint32 // field a comment
b [0]byte // field b comment
// other doc style comment
// and a last comment
}
================================================
FILE: go/analysis/passes/fieldalignment/testdata/src/a/a.go.golden
================================================
package a
type Good struct {
y int32
x byte
z byte
}
type Bad struct {
y int32
x byte
z byte
}
type ZeroGood struct {
a [0]byte
b uint32
}
type ZeroBad struct {
b [0]byte
a uint32
}
type NoNameGood struct {
Good
y int32
x byte
z byte
}
type NoNameBad struct {
Good
y int32
x byte
z byte
}
type WithComments struct {
b [0]byte
a uint32
}
================================================
FILE: go/analysis/passes/fieldalignment/testdata/src/a/a_386.go
================================================
package a
type PointerGood struct {
P *int
buf [1000]uintptr
}
type PointerBad struct { // want "struct with 4004 pointer bytes could be 4"
buf [1000]uintptr
P *int
}
type PointerSorta struct {
a struct {
p *int
q uintptr
}
b struct {
p *int
q [2]uintptr
}
}
type PointerSortaBad struct { // want "struct with 16 pointer bytes could be 12"
a struct {
p *int
q [2]uintptr
}
b struct {
p *int
q uintptr
}
}
type MultiField struct { // want "struct of size 20 could be 12"
b bool
i1, i2 int
a3 [3]bool
_ [0]func()
}
================================================
FILE: go/analysis/passes/fieldalignment/testdata/src/a/a_386.go.golden
================================================
package a
type PointerGood struct {
P *int
buf [1000]uintptr
}
type PointerBad struct {
P *int
buf [1000]uintptr
}
type PointerSorta struct {
a struct {
p *int
q uintptr
}
b struct {
p *int
q [2]uintptr
}
}
type PointerSortaBad struct {
b struct {
p *int
q uintptr
}
a struct {
p *int
q [2]uintptr
}
}
type MultiField struct {
_ [0]func()
i1 int
i2 int
a3 [3]bool
b bool
}
================================================
FILE: go/analysis/passes/fieldalignment/testdata/src/a/a_amd64.go
================================================
package a
type PointerGood struct {
P *int
buf [1000]uintptr
}
type PointerBad struct { // want "struct with 8008 pointer bytes could be 8"
buf [1000]uintptr
P *int
}
type PointerSorta struct {
a struct {
p *int
q uintptr
}
b struct {
p *int
q [2]uintptr
}
}
type PointerSortaBad struct { // want "struct with 32 pointer bytes could be 24"
a struct {
p *int
q [2]uintptr
}
b struct {
p *int
q uintptr
}
}
type MultiField struct { // want "struct of size 40 could be 24"
b bool
i1, i2 int
a3 [3]bool
_ [0]func()
}
type Issue43233 struct { // want "struct with 88 pointer bytes could be 80"
AllowedEvents []*string // allowed events
BlockedEvents []*string // blocked events
APIVersion string `mapstructure:"api_version"`
BaseURL string `mapstructure:"base_url"`
AccessToken string `mapstructure:"access_token"`
}
================================================
FILE: go/analysis/passes/fieldalignment/testdata/src/a/a_amd64.go.golden
================================================
package a
type PointerGood struct {
P *int
buf [1000]uintptr
}
type PointerBad struct {
P *int
buf [1000]uintptr
}
type PointerSorta struct {
a struct {
p *int
q uintptr
}
b struct {
p *int
q [2]uintptr
}
}
type PointerSortaBad struct {
b struct {
p *int
q uintptr
}
a struct {
p *int
q [2]uintptr
}
}
type MultiField struct {
_ [0]func()
i1 int
i2 int
a3 [3]bool
b bool
}
type Issue43233 struct {
APIVersion string `mapstructure:"api_version"`
BaseURL string `mapstructure:"base_url"`
AccessToken string `mapstructure:"access_token"`
AllowedEvents []*string
BlockedEvents []*string
}
================================================
FILE: go/analysis/passes/findcall/cmd/findcall/main.go
================================================
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// The findcall command runs the findcall analyzer.
package main
import (
"golang.org/x/tools/go/analysis/passes/findcall"
"golang.org/x/tools/go/analysis/singlechecker"
)
func main() { singlechecker.Main(findcall.Analyzer) }
================================================
FILE: go/analysis/passes/findcall/findcall.go
================================================
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package findcall defines an Analyzer that serves as a trivial
// example and test of the Analysis API. It reports a diagnostic for
// every call to a function or method of the name specified by its
// -name flag. It also exports a fact for each declaration that
// matches the name, plus a package-level fact if the package contained
// one or more such declarations.
package findcall
import (
"fmt"
"go/ast"
"go/types"
"golang.org/x/tools/go/analysis"
)
const Doc = `find calls to a particular function
The findcall analysis reports calls to functions or methods
of a particular name.`
var Analyzer = &analysis.Analyzer{
Name: "findcall",
Doc: Doc,
URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/findcall",
Run: run,
RunDespiteErrors: true,
FactTypes: []analysis.Fact{new(foundFact)},
}
var name string // -name flag
func init() {
Analyzer.Flags.StringVar(&name, "name", name, "name of the function to find")
}
func run(pass *analysis.Pass) (any, error) {
for _, f := range pass.Files {
ast.Inspect(f, func(n ast.Node) bool {
if call, ok := n.(*ast.CallExpr); ok {
var id *ast.Ident
switch fun := call.Fun.(type) {
case *ast.Ident:
id = fun
case *ast.SelectorExpr:
id = fun.Sel
}
if id != nil && !pass.TypesInfo.Types[id].IsType() && id.Name == name {
pass.Report(analysis.Diagnostic{
Pos: call.Lparen,
Message: fmt.Sprintf("call of %s(...)", id.Name),
SuggestedFixes: []analysis.SuggestedFix{{
Message: fmt.Sprintf("Add '_TEST_'"),
TextEdits: []analysis.TextEdit{{
Pos: call.Lparen,
End: call.Lparen,
NewText: []byte("_TEST_"),
}},
}},
})
}
}
return true
})
}
// Export a fact for each matching function.
//
// These facts are produced only to test the testing
// infrastructure in the analysistest package.
// They are not consumed by the findcall Analyzer
// itself, as would happen in a more realistic example.
for _, f := range pass.Files {
for _, decl := range f.Decls {
if decl, ok := decl.(*ast.FuncDecl); ok && decl.Name.Name == name {
if obj, ok := pass.TypesInfo.Defs[decl.Name].(*types.Func); ok {
pass.ExportObjectFact(obj, new(foundFact))
}
}
}
}
if len(pass.AllObjectFacts()) > 0 {
pass.ExportPackageFact(new(foundFact))
}
return nil, nil
}
// foundFact is a fact associated with functions that match -name.
// We use it to exercise the fact machinery in tests.
type foundFact struct{}
func (*foundFact) String() string { return "found" }
func (*foundFact) AFact() {}
================================================
FILE: go/analysis/passes/findcall/findcall_test.go
================================================
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package findcall_test
import (
"testing"
"golang.org/x/tools/go/analysis/analysistest"
"golang.org/x/tools/go/analysis/passes/findcall"
)
func init() {
findcall.Analyzer.Flags.Set("name", "println")
}
// TestFromStringLiterals demonstrates how to test an analysis using
// a table of string literals for each test case.
//
// Such tests are typically quite compact.
func TestFromStringLiterals(t *testing.T) {
for _, test := range [...]struct {
desc string
pkgpath string
files map[string]string
}{
{
desc: "SimpleTest",
pkgpath: "main",
files: map[string]string{"main/main.go": `package main // want package:"found"
func main() {
println("hello") // want "call of println"
print("goodbye") // not a call of println
}
func println(s string) {} // want println:"found"`,
},
},
} {
t.Run(test.desc, func(t *testing.T) {
dir, cleanup, err := analysistest.WriteFiles(test.files)
if err != nil {
t.Fatal(err)
}
defer cleanup()
analysistest.Run(t, dir, findcall.Analyzer, test.pkgpath)
})
}
}
// TestFromFileSystem demonstrates how to test an analysis using input
// files stored in the file system.
//
// These tests have the advantages that test data can be edited
// directly, and that files named in error messages can be opened.
// However, they tend to spread a small number of lines of text across a
// rather deep directory hierarchy, and obscure similarities among
// related tests, especially when tests involve multiple packages, or
// multiple variants of a single scenario.
func TestFromFileSystem(t *testing.T) {
testdata := analysistest.TestData()
analysistest.RunWithSuggestedFixes(t, testdata, findcall.Analyzer, "a") // loads testdata/src/a/a.go.
}
================================================
FILE: go/analysis/passes/findcall/testdata/src/a/a.go
================================================
package main // want package:"found"
func main() {
println("hi") // want "call of println"
print("hi") // not a call of println
}
func println(s string) {} // want println:"found"
================================================
FILE: go/analysis/passes/findcall/testdata/src/a/a.go.golden
================================================
package main // want package:"found"
func main() {
println_TEST_("hi") // want "call of println"
print("hi") // not a call of println
}
func println(s string) {} // want println:"found"
================================================
FILE: go/analysis/passes/framepointer/framepointer.go
================================================
// Copyright 2020 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package framepointer defines an Analyzer that reports assembly code
// that clobbers the frame pointer before saving it.
package framepointer
import (
"go/build"
"regexp"
"strings"
"unicode"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/internal/analysis/analyzerutil"
)
const Doc = "report assembly that clobbers the frame pointer before saving it"
var Analyzer = &analysis.Analyzer{
Name: "framepointer",
Doc: Doc,
URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/framepointer",
Run: run,
}
// Per-architecture checks for instructions.
// Assume comments, leading and trailing spaces are removed.
type arch struct {
isFPWrite func(string) bool
isFPRead func(string) bool
isUnconditionalBranch func(string) bool
}
var re = regexp.MustCompile
func hasAnyPrefix(s string, prefixes ...string) bool {
for _, p := range prefixes {
if strings.HasPrefix(s, p) {
return true
}
}
return false
}
var arches = map[string]arch{
"amd64": {
isFPWrite: re(`,\s*BP$`).MatchString, // TODO: can have false positive, e.g. for TESTQ BP,BP. Seems unlikely.
isFPRead: re(`\bBP\b`).MatchString,
isUnconditionalBranch: func(s string) bool {
return hasAnyPrefix(s, "JMP", "RET")
},
},
"arm64": {
isFPWrite: func(s string) bool {
if i := strings.LastIndex(s, ","); i > 0 && strings.HasSuffix(s[i:], "R29") {
return true
}
if hasAnyPrefix(s, "LDP", "LDAXP", "LDXP", "CASP") {
// Instructions which write to a pair of registers, e.g.
// LDP 8(R0), (R26, R29)
// CASPD (R2, R3), (R2), (R26, R29)
lp := strings.LastIndex(s, "(")
rp := strings.LastIndex(s, ")")
if lp > -1 && lp < rp {
return strings.Contains(s[lp:rp], ",") && strings.Contains(s[lp:rp], "R29")
}
}
return false
},
isFPRead: re(`\bR29\b`).MatchString,
isUnconditionalBranch: func(s string) bool {
// Get just the instruction
if i := strings.IndexFunc(s, unicode.IsSpace); i > 0 {
s = s[:i]
}
return s == "B" || s == "JMP" || s == "RET"
},
},
}
func run(pass *analysis.Pass) (any, error) {
arch, ok := arches[build.Default.GOARCH]
if !ok {
return nil, nil
}
if build.Default.GOOS != "linux" && build.Default.GOOS != "darwin" {
return nil, nil
}
// Find assembly files to work on.
var sfiles []string
for _, fname := range pass.OtherFiles {
if strings.HasSuffix(fname, ".s") && pass.Pkg.Path() != "runtime" {
sfiles = append(sfiles, fname)
}
}
for _, fname := range sfiles {
content, tf, err := analyzerutil.ReadFile(pass, fname)
if err != nil {
return nil, err
}
lines := strings.SplitAfter(string(content), "\n")
active := false
for lineno, line := range lines {
lineno++
// Ignore comments and commented-out code.
if i := strings.Index(line, "//"); i >= 0 {
line = line[:i]
}
line = strings.TrimSpace(line)
if line == "" {
continue
}
// We start checking code at a TEXT line for a frameless function.
if strings.HasPrefix(line, "TEXT") && strings.Contains(line, "(SB)") && strings.Contains(line, "$0") {
active = true
continue
}
if !active {
continue
}
if arch.isFPWrite(line) {
pass.Reportf(tf.LineStart(lineno), "frame pointer is clobbered before saving")
active = false
continue
}
if arch.isFPRead(line) || arch.isUnconditionalBranch(line) {
active = false
continue
}
}
}
return nil, nil
}
================================================
FILE: go/analysis/passes/framepointer/framepointer_test.go
================================================
// Copyright 2020 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package framepointer_test
import (
"go/build"
"testing"
"golang.org/x/tools/go/analysis/analysistest"
"golang.org/x/tools/go/analysis/passes/framepointer"
)
func Test(t *testing.T) {
if build.Default.GOOS != "linux" && build.Default.GOOS != "darwin" {
// The test has an os-generic assembly file, testdata/a/asm_amd64.s.
// It should produce errors on linux or darwin, but not on other archs.
// Unfortunately, there's no way to say that in the "want" comments
// in that file. So we skip testing on other GOOSes. The framepointer
// analyzer should not report any errors on those GOOSes, so it's not
// really a hard test on those platforms.
t.Skipf("test for GOOS=%s is not implemented", build.Default.GOOS)
}
analysistest.Run(t, analysistest.TestData(), framepointer.Analyzer, "a")
}
================================================
FILE: go/analysis/passes/framepointer/testdata/src/a/asm.go
================================================
// Copyright 2020 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package a
================================================
FILE: go/analysis/passes/framepointer/testdata/src/a/asm_amd64.s
================================================
// Copyright 2020 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
TEXT ·bad1(SB), 0, $0
MOVQ $0, BP // want `frame pointer is clobbered before saving`
RET
TEXT ·bad2(SB), 0, $0
MOVQ AX, BP // want `frame pointer is clobbered before saving`
RET
TEXT ·bad3(SB), 0, $0
MOVQ 6(AX), BP // want `frame pointer is clobbered before saving`
RET
TEXT ·bad4(SB), 0, $0
CMPQ AX, BX
JEQ skip
// Assume the above conditional branch is not taken
MOVQ $0, BP // want `frame pointer is clobbered before saving`
skip:
RET
TEXT ·good1(SB), 0, $0
PUSHQ BP
MOVQ $0, BP // this is ok
POPQ BP
RET
TEXT ·good2(SB), 0, $0
MOVQ BP, BX
MOVQ $0, BP // this is ok
MOVQ BX, BP
RET
TEXT ·good3(SB), 0, $0
CMPQ AX, BX
JMP skip
MOVQ $0, BP // this is ok
skip:
RET
TEXT ·good4(SB), 0, $0
RET
MOVQ $0, BP // this is ok
RET
TEXT ·good5(SB), 0, $8
MOVQ $0, BP // this is ok
RET
================================================
FILE: go/analysis/passes/framepointer/testdata/src/a/asm_arm64.s
================================================
// Copyright 2024 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
TEXT ·bad1(SB), 0, $0
MOVD $0, R29 // want `frame pointer is clobbered before saving`
RET
TEXT ·bad2(SB), 0, $0
MOVD R1, R29 // want `frame pointer is clobbered before saving`
RET
TEXT ·bad3(SB), 0, $0
MOVD 6(R2), R29 // want `frame pointer is clobbered before saving`
RET
TEXT ·bad4(SB), 0, $0
LDP 0(R1), (R26, R29) // want `frame pointer is clobbered before saving`
RET
TEXT ·bad5(SB), 0, $0
AND $0x1, R3, R29 // want `frame pointer is clobbered before saving`
RET
TEXT ·bad6(SB), 0, $0
CMP R1, R2
BEQ skip
// Assume that the above conditional branch is not taken
MOVD $0, R29 // want `frame pointer is clobbered before saving`
skip:
RET
TEXT ·bad7(SB), 0, $0
BL ·good4(SB)
AND $0x1, R3, R29 // want `frame pointer is clobbered before saving`
RET
TEXT ·good1(SB), 0, $0
STPW (R29, R30), -32(RSP)
MOVD $0, R29 // this is ok
LDPW 32(RSP), (R29, R30)
RET
TEXT ·good2(SB), 0, $0
MOVD R29, R1
MOVD $0, R29 // this is ok
MOVD R1, R29
RET
TEXT ·good3(SB), 0, $0
CMP R1, R2
B skip
MOVD $0, R29 // this is ok
skip:
RET
TEXT ·good4(SB), 0, $0
RET
MOVD $0, R29 // this is ok
RET
TEXT ·good5(SB), 0, $8
MOVD $0, R29 // this is ok
RET
================================================
FILE: go/analysis/passes/framepointer/testdata/src/a/asm_darwin_amd64.s
================================================
// Copyright 2020 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
TEXT ·z1(SB), 0, $0
MOVQ $0, BP // want `frame pointer is clobbered before saving`
RET
================================================
FILE: go/analysis/passes/framepointer/testdata/src/a/asm_linux_amd64.s
================================================
// Copyright 2020 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
TEXT ·z1(SB), 0, $0
MOVQ $0, BP // want `frame pointer is clobbered before saving`
RET
================================================
FILE: go/analysis/passes/framepointer/testdata/src/a/asm_windows_amd64.s
================================================
// Copyright 2020 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
TEXT ·z1(SB), 0, $0
MOVQ $0, BP // not an error on windows
RET
================================================
FILE: go/analysis/passes/framepointer/testdata/src/a/buildtag_amd64.s
================================================
// Copyright 2020 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build nope
TEXT ·bt1(SB), 0, $0
MOVQ $0, BP // ok because of build tag
RET
================================================
FILE: go/analysis/passes/gofix/doc.go
================================================
// Copyright 2025 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
/*
Package gofix defines an Analyzer that checks "//go:fix inline" directives.
See golang.org/x/tools/go/analysis/passes/inline/doc.go for details.
# Analyzer gofixdirective
gofixdirective: validate uses of gofix comment directives
The gofixdirective analyzer checks "//go:fix inline" directives for correctness.
The proposal https://go.dev/issue/32816 introduces the "//go:fix" directives.
The analyzer checks for the following issues:
- A constant definition can be marked for inlining only if it refers to another
named constant.
//go:fix inline
const (
a = 1 // error
b = iota // error
c = a // OK
d = math.Pi // OK
)
- A type definition can be marked for inlining only if it is an alias.
//go:fix inline
type (
T int // error
A = int // OK
)
- An alias whose right-hand side contains a non-literal array size
cannot be marked for inlining.
const two = 2
//go:fix inline
type (
A = []int // OK
B = [1]int // OK
C = [two]int // error
)
*/
package gofix
================================================
FILE: go/analysis/passes/gofix/gofix.go
================================================
// Copyright 2025 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package gofix defines an analyzer that checks go:fix directives.
package gofix
import (
_ "embed"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/analysis/passes/internal/gofixdirective"
"golang.org/x/tools/go/ast/inspector"
"golang.org/x/tools/internal/analysis/analyzerutil"
)
//go:embed doc.go
var doc string
var Analyzer = &analysis.Analyzer{
Name: "gofixdirective",
Doc: analyzerutil.MustExtractDoc(doc, "gofixdirective"),
URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/gofix",
Run: run,
Requires: []*analysis.Analyzer{inspect.Analyzer},
}
func run(pass *analysis.Pass) (any, error) {
root := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector).Root()
gofixdirective.Find(pass, root, nil)
return nil, nil
}
================================================
FILE: go/analysis/passes/gofix/gofix_test.go
================================================
// Copyright 2025 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package gofix_test
import (
"testing"
"golang.org/x/tools/go/analysis/analysistest"
"golang.org/x/tools/go/analysis/passes/gofix"
)
func Test(t *testing.T) {
testdata := analysistest.TestData()
analysistest.Run(t, testdata, gofix.Analyzer, "a")
}
================================================
FILE: go/analysis/passes/gofix/testdata/src/a/a.go
================================================
// Copyright 2023 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// This file contains tests for the gofix checker.
package a
const one = 1
//go:fix inline
const (
in3 = one
in4 = one
bad1 = 1 // want `invalid //go:fix inline directive: const value is not the name of another constant`
)
//go:fix inline
const in5,
in6,
bad2 = one, one,
one + 1 // want `invalid //go:fix inline directive: const value is not the name of another constant`
//go:fix inline
const (
a = iota // want `invalid //go:fix inline directive: const value is iota`
b
in7 = one
)
func shadow() {
//go:fix inline
const a = iota // want `invalid //go:fix inline directive: const value is iota`
const iota = 2
//go:fix inline
const b = iota // not an error: iota is not the builtin
}
// Type aliases
//go:fix inline
type A int // want `invalid //go:fix inline directive: not a type alias`
//go:fix inline
type E = map[[one]string][]int // want `invalid //go:fix inline directive: array types not supported`
================================================
FILE: go/analysis/passes/hostport/hostport.go
================================================
// Copyright 2024 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package hostport defines an analyzer for calls to net.Dial with
// addresses of the form "%s:%d" or "%s:%s", which work only with IPv4.
package hostport
import (
"fmt"
"go/ast"
"go/constant"
"go/token"
"go/types"
"strconv"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/types/typeutil"
typeindexanalyzer "golang.org/x/tools/internal/analysis/typeindex"
"golang.org/x/tools/internal/typesinternal/typeindex"
)
const Doc = `check format of addresses passed to net.Dial
This analyzer flags code that produce network address strings using
fmt.Sprintf, as in this example:
addr := fmt.Sprintf("%s:%d", host, 12345) // "will not work with IPv6"
...
conn, err := net.Dial("tcp", addr) // "when passed to dial here"
The analyzer suggests a fix to use the correct approach, a call to
net.JoinHostPort:
addr := net.JoinHostPort(host, "12345")
...
conn, err := net.Dial("tcp", addr)
A similar diagnostic and fix are produced for a format string of "%s:%s".
`
var Analyzer = &analysis.Analyzer{
Name: "hostport",
Doc: Doc,
URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/hostport",
Requires: []*analysis.Analyzer{inspect.Analyzer, typeindexanalyzer.Analyzer},
Run: run,
}
func run(pass *analysis.Pass) (any, error) {
var (
index = pass.ResultOf[typeindexanalyzer.Analyzer].(*typeindex.Index)
info = pass.TypesInfo
fmtSprintf = index.Object("fmt", "Sprintf")
)
if !index.Used(fmtSprintf) {
return nil, nil // fast path: package doesn't use fmt.Sprintf
}
// checkAddr reports a diagnostic (and returns true) if e
// is a call of the form fmt.Sprintf("%s:%d", ...).
// The diagnostic includes a fix.
//
// dialCall is non-nil if the Dial call is non-local
// but within the same file.
checkAddr := func(e ast.Expr, dialCall *ast.CallExpr) {
if call, ok := e.(*ast.CallExpr); ok &&
len(call.Args) == 3 &&
typeutil.Callee(info, call) == fmtSprintf {
// Examine format string.
formatArg := call.Args[0]
if tv := info.Types[formatArg]; tv.Value != nil {
numericPort := false
format := constant.StringVal(tv.Value)
switch format {
case "%s:%d":
// Have: fmt.Sprintf("%s:%d", host, port)
numericPort = true
case "%s:%s":
// Have: fmt.Sprintf("%s:%s", host, portStr)
// Keep port string as is.
default:
return
}
// Use granular edits to preserve original formatting.
edits := []analysis.TextEdit{
{
// Replace fmt.Sprintf with net.JoinHostPort.
Pos: call.Fun.Pos(),
End: call.Fun.End(),
NewText: []byte("net.JoinHostPort"),
},
{
// Delete format string.
Pos: formatArg.Pos(),
End: call.Args[1].Pos(),
},
}
// Turn numeric port into a string.
if numericPort {
port := call.Args[2]
// Is port an integer literal?
//
// (Don't allow arbitrary constants k otherwise the
// transformation k => fmt.Sprintf("%d", "123")
// loses the symbolic connection to k.)
var kPort int64 = -1
if lit, ok := port.(*ast.BasicLit); ok && lit.Kind == token.INT {
if v, err := strconv.ParseInt(lit.Value, 0, 64); err == nil {
kPort = v
}
}
if kPort >= 0 {
// literal: 0x7B => "123"
edits = append(edits, analysis.TextEdit{
Pos: port.Pos(),
End: port.End(),
NewText: fmt.Appendf(nil, `"%d"`, kPort), // (decimal)
})
} else {
// non-literal: port => fmt.Sprintf("%d", port)
edits = append(edits, []analysis.TextEdit{
{
Pos: port.Pos(),
End: port.Pos(),
NewText: []byte(`fmt.Sprintf("%d", `),
},
{
Pos: port.End(),
End: port.End(),
NewText: []byte(`)`),
},
}...)
}
}
// Refer to Dial call, if not adjacent.
suffix := ""
if dialCall != nil {
suffix = fmt.Sprintf(" (passed to net.Dial at L%d)",
pass.Fset.Position(dialCall.Pos()).Line)
}
pass.Report(analysis.Diagnostic{
// Highlight the format string.
Pos: formatArg.Pos(),
End: formatArg.End(),
Message: fmt.Sprintf("address format %q does not work with IPv6%s", format, suffix),
SuggestedFixes: []analysis.SuggestedFix{{
Message: "Replace fmt.Sprintf with net.JoinHostPort",
TextEdits: edits,
}},
})
}
}
}
// Check address argument of each call to net.Dial et al.
for _, callee := range []types.Object{
index.Object("net", "Dial"),
index.Object("net", "DialTimeout"),
index.Selection("net", "Dialer", "Dial"),
} {
for curCall := range index.Calls(callee) {
call := curCall.Node().(*ast.CallExpr)
switch address := call.Args[1].(type) {
case *ast.CallExpr:
if len(call.Args) == 2 { // avoid spread-call edge case
// net.Dial("tcp", fmt.Sprintf("%s:%d", ...))
checkAddr(address, nil)
}
case *ast.Ident:
// addr := fmt.Sprintf("%s:%d", ...)
// ...
// net.Dial("tcp", addr)
// Search for decl of addrVar within common ancestor of addrVar and Dial call.
// TODO(adonovan): abstract "find RHS of statement that assigns var v".
// TODO(adonovan): reject if there are other assignments to var v.
if addrVar, ok := info.Uses[address].(*types.Var); ok {
if curId, ok := index.Def(addrVar); ok {
// curIdent is the declaring ast.Ident of addr.
switch parent := curId.Parent().Node().(type) {
case *ast.AssignStmt:
if len(parent.Rhs) == 1 {
// Have: addr := fmt.Sprintf("%s:%d", ...)
checkAddr(parent.Rhs[0], call)
}
case *ast.ValueSpec:
if len(parent.Values) == 1 {
// Have: var addr = fmt.Sprintf("%s:%d", ...)
checkAddr(parent.Values[0], call)
}
}
}
}
}
}
}
return nil, nil
}
================================================
FILE: go/analysis/passes/hostport/hostport_test.go
================================================
// Copyright 2024 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package hostport_test
import (
"testing"
"golang.org/x/tools/go/analysis/analysistest"
"golang.org/x/tools/go/analysis/passes/hostport"
)
func Test(t *testing.T) {
testdata := analysistest.TestData()
analysistest.RunWithSuggestedFixes(t, testdata, hostport.Analyzer, "a")
}
================================================
FILE: go/analysis/passes/hostport/main.go
================================================
// Copyright 2024 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build ignore
package main
import (
"golang.org/x/tools/go/analysis/singlechecker"
"golang.org/x/tools/gopls/internal/analysis/hostport"
)
func main() { singlechecker.Main(hostport.Analyzer) }
================================================
FILE: go/analysis/passes/hostport/testdata/src/a/a.go
================================================
package a
import (
"fmt"
"net"
)
func direct(host string, port int, portStr string) {
// Dial, directly called with result of Sprintf.
net.Dial("tcp", fmt.Sprintf("%s:%d", host, port)) // want `address format "%s:%d" does not work with IPv6`
net.Dial("tcp", fmt.Sprintf("%s:%s", host, portStr)) // want `address format "%s:%s" does not work with IPv6`
}
// port is a literal:
var addr4 = fmt.Sprintf("%s:%d", "localhost", 123) // want `address format "%s:%d" does not work with IPv6 \(passed to net.Dial at L39\)`
func indirect(host string, port int) {
// Dial, addr is immediately preceding.
{
addr1 := fmt.Sprintf("%s:%d", host, port) // want `address format "%s:%d" does not work with IPv6.*at L22`
net.Dial("tcp", addr1)
}
// DialTimeout, addr is in ancestor block.
addr2 := fmt.Sprintf("%s:%d", host, port) // want `address format "%s:%d" does not work with IPv6.*at L28`
{
net.DialTimeout("tcp", addr2, 0)
}
// Dialer.Dial, addr is declared with var.
var dialer net.Dialer
{
var addr3 = fmt.Sprintf("%s:%d", host, port) // want `address format "%s:%d" does not work with IPv6.*at L35`
dialer.Dial("tcp", addr3)
}
// Dialer.Dial again, addr is declared at package level.
dialer.Dial("tcp", addr4)
}
// Regression tests for crashes in well-typed code that nonetheless mis-uses Sprintf:
// too few arguments, or port is not an integer.
var (
_, _ = net.Dial("tcp", fmt.Sprintf("%s:%d"))
_, _ = net.Dial("tcp", fmt.Sprintf("%s:%d", "host"))
_, _ = net.Dial("tcp", fmt.Sprintf("%s:%d", "host", "port")) // want `address format "%s:%d" does not work with IPv6`
)
func _() {
// port is a non-constant literal
const port = 0x7B
_, _ = net.Dial("tcp", fmt.Sprintf("%s:%d", "localhost", port)) // want `address format "%s:%d" does not work with IPv6`
}
================================================
FILE: go/analysis/passes/hostport/testdata/src/a/a.go.golden
================================================
package a
import (
"fmt"
"net"
)
func direct(host string, port int, portStr string) {
// Dial, directly called with result of Sprintf.
net.Dial("tcp", net.JoinHostPort(host, fmt.Sprintf("%d", port))) // want `address format "%s:%d" does not work with IPv6`
net.Dial("tcp", net.JoinHostPort(host, portStr)) // want `address format "%s:%s" does not work with IPv6`
}
// port is a literal:
var addr4 = net.JoinHostPort("localhost", "123") // want `address format "%s:%d" does not work with IPv6 \(passed to net.Dial at L39\)`
func indirect(host string, port int) {
// Dial, addr is immediately preceding.
{
addr1 := net.JoinHostPort(host, fmt.Sprintf("%d", port)) // want `address format "%s:%d" does not work with IPv6.*at L22`
net.Dial("tcp", addr1)
}
// DialTimeout, addr is in ancestor block.
addr2 := net.JoinHostPort(host, fmt.Sprintf("%d", port)) // want `address format "%s:%d" does not work with IPv6.*at L28`
{
net.DialTimeout("tcp", addr2, 0)
}
// Dialer.Dial, addr is declared with var.
var dialer net.Dialer
{
var addr3 = net.JoinHostPort(host, fmt.Sprintf("%d", port)) // want `address format "%s:%d" does not work with IPv6.*at L35`
dialer.Dial("tcp", addr3)
}
// Dialer.Dial again, addr is declared at package level.
dialer.Dial("tcp", addr4)
}
// Regression tests for crashes in well-typed code that nonetheless mis-uses Sprintf:
// too few arguments, or port is not an integer.
var (
_, _ = net.Dial("tcp", fmt.Sprintf("%s:%d"))
_, _ = net.Dial("tcp", fmt.Sprintf("%s:%d", "host"))
_, _ = net.Dial("tcp", net.JoinHostPort("host", fmt.Sprintf("%d", "port"))) // want `address format "%s:%d" does not work with IPv6`
)
func _() {
// port is a non-constant literal
const port = 0x7B
_, _ = net.Dial("tcp", net.JoinHostPort("localhost", fmt.Sprintf("%d", port))) // want `address format "%s:%d" does not work with IPv6`
}
================================================
FILE: go/analysis/passes/httpmux/cmd/httpmux/main.go
================================================
// Copyright 2023 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// The httpmux command runs the httpmux analyzer.
package main
import (
"golang.org/x/tools/go/analysis/passes/httpmux"
"golang.org/x/tools/go/analysis/singlechecker"
)
func main() { singlechecker.Main(httpmux.Analyzer) }
================================================
FILE: go/analysis/passes/httpmux/httpmux.go
================================================
// Copyright 2023 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package httpmux
import (
"go/ast"
"go/constant"
"go/types"
"regexp"
"slices"
"strings"
"golang.org/x/mod/semver"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/ast/inspector"
"golang.org/x/tools/go/types/typeutil"
"golang.org/x/tools/internal/typesinternal"
)
const Doc = `report using Go 1.22 enhanced ServeMux patterns in older Go versions
The httpmux analysis is active for Go modules configured to run with Go 1.21 or
earlier versions. It reports calls to net/http.ServeMux.Handle and HandleFunc
methods whose patterns use features added in Go 1.22, like HTTP methods (such as
"GET") and wildcards. (See https://pkg.go.dev/net/http#ServeMux for details.)
Such patterns can be registered in older versions of Go, but will not behave as expected.`
var Analyzer = &analysis.Analyzer{
Name: "httpmux",
Doc: Doc,
URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/httpmux",
Requires: []*analysis.Analyzer{inspect.Analyzer},
Run: run,
}
var inTest bool // So Go version checks can be skipped during testing.
func run(pass *analysis.Pass) (any, error) {
if !inTest {
// Check that Go version is 1.21 or earlier.
if goVersionAfter121(goVersion(pass.Pkg)) {
return nil, nil
}
}
if !typesinternal.Imports(pass.Pkg, "net/http") {
return nil, nil
}
// Look for calls to ServeMux.Handle or ServeMux.HandleFunc.
inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
nodeFilter := []ast.Node{
(*ast.CallExpr)(nil),
}
inspect.Preorder(nodeFilter, func(n ast.Node) {
call := n.(*ast.CallExpr)
if isServeMuxRegisterCall(pass, call) {
pat, ok := stringConstantExpr(pass, call.Args[0])
if ok && likelyEnhancedPattern(pat) {
pass.ReportRangef(call.Args[0], "possible enhanced ServeMux pattern used with Go version before 1.22 (update go.mod file?)")
}
}
})
return nil, nil
}
// isServeMuxRegisterCall reports whether call is a static call to one of:
// - net/http.Handle
// - net/http.HandleFunc
// - net/http.ServeMux.Handle
// - net/http.ServeMux.HandleFunc
// TODO(jba): consider expanding this to accommodate wrappers around these functions.
func isServeMuxRegisterCall(pass *analysis.Pass, call *ast.CallExpr) bool {
fn := typeutil.StaticCallee(pass.TypesInfo, call)
if fn == nil {
return false
}
if typesinternal.IsFunctionNamed(fn, "net/http", "Handle", "HandleFunc") {
return true
}
if !isMethodNamed(fn, "net/http", "Handle", "HandleFunc") {
return false
}
recv := fn.Signature().Recv() // isMethodNamed() -> non-nil
isPtr, named := typesinternal.ReceiverNamed(recv)
return isPtr && typesinternal.IsTypeNamed(named, "net/http", "ServeMux")
}
// isMethodNamed reports when a function f is a method,
// in a package with the path pkgPath and the name of f is in names.
//
// (Unlike [analysis.IsMethodNamed], it ignores the receiver type name.)
func isMethodNamed(f *types.Func, pkgPath string, names ...string) bool {
if f == nil {
return false
}
if f.Pkg() == nil || f.Pkg().Path() != pkgPath {
return false // not at pkgPath
}
if f.Signature().Recv() == nil {
return false // not a method
}
return slices.Contains(names, f.Name())
}
// stringConstantExpr returns expression's string constant value.
//
// ("", false) is returned if expression isn't a string
// constant.
func stringConstantExpr(pass *analysis.Pass, expr ast.Expr) (string, bool) {
lit := pass.TypesInfo.Types[expr].Value
if lit != nil && lit.Kind() == constant.String {
return constant.StringVal(lit), true
}
return "", false
}
// A valid wildcard must start a segment, and its name must be valid Go
// identifier.
var wildcardRegexp = regexp.MustCompile(`/\{[_\pL][_\pL\p{Nd}]*(\.\.\.)?\}`)
// likelyEnhancedPattern reports whether the ServeMux pattern pat probably
// contains either an HTTP method name or a wildcard, extensions added in Go 1.22.
func likelyEnhancedPattern(pat string) bool {
if strings.Contains(pat, " ") {
// A space in the pattern suggests that it begins with an HTTP method.
return true
}
return wildcardRegexp.MatchString(pat)
}
func goVersionAfter121(goVersion string) bool {
if goVersion == "" { // Maybe the stdlib?
return true
}
version := versionFromGoVersion(goVersion)
return semver.Compare(version, "v1.21") > 0
}
func goVersion(pkg *types.Package) string {
// types.Package.GoVersion did not exist before Go 1.21.
if p, ok := any(pkg).(interface{ GoVersion() string }); ok {
return p.GoVersion()
}
return ""
}
var (
// Regexp for matching go tags. The groups are:
// 1 the major.minor version
// 2 the patch version, or empty if none
// 3 the entire prerelease, if present
// 4 the prerelease type ("beta" or "rc")
// 5 the prerelease number
tagRegexp = regexp.MustCompile(`^go(\d+\.\d+)(\.\d+|)((beta|rc)(\d+))?$`)
)
// Copied from pkgsite/internal/stdlib.VersionForTag.
func versionFromGoVersion(goVersion string) string {
// Special cases for go1.
if goVersion == "go1" {
return "v1.0.0"
}
if goVersion == "go1.0" {
return ""
}
m := tagRegexp.FindStringSubmatch(goVersion)
if m == nil {
return ""
}
version := "v" + m[1]
if m[2] != "" {
version += m[2]
} else {
version += ".0"
}
if m[3] != "" {
version += "-" + m[4] + "." + m[5]
}
return version
}
================================================
FILE: go/analysis/passes/httpmux/httpmux_test.go
================================================
// Copyright 2023 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package httpmux
import (
"testing"
"golang.org/x/tools/go/analysis/analysistest"
)
func Test(t *testing.T) {
testdata := analysistest.TestData()
tests := []string{"a"}
inTest = true
analysistest.Run(t, testdata, Analyzer, tests...)
}
func TestGoVersion(t *testing.T) {
for _, test := range []struct {
in string
want bool
}{
{"", true},
{"go1", false},
{"go1.21", false},
{"go1.21rc3", false},
{"go1.22", true},
{"go1.22rc1", true},
} {
got := goVersionAfter121(test.in)
if got != test.want {
t.Errorf("%q: got %t, want %t", test.in, got, test.want)
}
}
}
================================================
FILE: go/analysis/passes/httpmux/testdata/src/a/a.go
================================================
// Copyright 2023 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// This file contains tests for the httpmux checker.
package a
import "net/http"
func _() {
http.HandleFunc("GET /x", nil) // want "enhanced ServeMux pattern"
http.HandleFunc("/{a}/b/", nil) // want "enhanced ServeMux pattern"
mux := http.NewServeMux()
mux.Handle("example.com/c/{d}", nil) // want "enhanced ServeMux pattern"
mux.HandleFunc("/{x...}", nil) // want "enhanced ServeMux pattern"
// Should not match.
// not an enhanced pattern
http.Handle("/", nil)
// invalid wildcard; will panic in 1.22
http.HandleFunc("/{/a/}", nil)
mux.Handle("/{1}", nil)
mux.Handle("/x{a}", nil)
// right package, wrong method
http.ParseTime("GET /")
// right function name, wrong package
Handle("GET /", nil)
HandleFunc("GET /", nil)
// right method name, wrong type
var sm ServeMux
sm.Handle("example.com/c/{d}", nil)
sm.HandleFunc("method /{x...}", nil)
}
func Handle(pat string, x any) {}
func HandleFunc(pat string, x any) {}
type ServeMux struct{}
func (*ServeMux) Handle(pat string, x any) {}
func (*ServeMux) HandleFunc(pat string, x any) {}
================================================
FILE: go/analysis/passes/httpresponse/httpresponse.go
================================================
// Copyright 2016 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package httpresponse defines an Analyzer that checks for mistakes
// using HTTP responses.
package httpresponse
import (
"go/ast"
"go/types"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/ast/inspector"
"golang.org/x/tools/internal/typesinternal"
)
const Doc = `check for mistakes using HTTP responses
A common mistake when using the net/http package is to defer a function
call to close the http.Response Body before checking the error that
determines whether the response is valid:
resp, err := http.Head(url)
defer resp.Body.Close()
if err != nil {
log.Fatal(err)
}
// (defer statement belongs here)
This checker helps uncover latent nil dereference bugs by reporting a
diagnostic for such mistakes.`
var Analyzer = &analysis.Analyzer{
Name: "httpresponse",
Doc: Doc,
URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/httpresponse",
Requires: []*analysis.Analyzer{inspect.Analyzer},
Run: run,
}
func run(pass *analysis.Pass) (any, error) {
inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
// Fast path: if the package doesn't import net/http,
// skip the traversal.
if !typesinternal.Imports(pass.Pkg, "net/http") {
return nil, nil
}
nodeFilter := []ast.Node{
(*ast.CallExpr)(nil),
}
inspect.WithStack(nodeFilter, func(n ast.Node, push bool, stack []ast.Node) bool {
if !push {
return true
}
call := n.(*ast.CallExpr)
if !isHTTPFuncOrMethodOnClient(pass.TypesInfo, call) {
return true // the function call is not related to this check.
}
// Find the innermost containing block, and get the list
// of statements starting with the one containing call.
stmts, ncalls := restOfBlock(stack)
if len(stmts) < 2 {
// The call to the http function is the last statement of the block.
return true
}
// Skip cases in which the call is wrapped by another (#52661).
// Example: resp, err := checkError(http.Get(url))
if ncalls > 1 {
return true
}
asg, ok := stmts[0].(*ast.AssignStmt)
if !ok {
return true // the first statement is not assignment.
}
resp := rootIdent(asg.Lhs[0])
if resp == nil {
return true // could not find the http.Response in the assignment.
}
def, ok := stmts[1].(*ast.DeferStmt)
if !ok {
return true // the following statement is not a defer.
}
root := rootIdent(def.Call.Fun)
if root == nil {
return true // could not find the receiver of the defer call.
}
if resp.Obj == root.Obj {
pass.ReportRangef(root, "using %s before checking for errors", resp.Name)
}
return true
})
return nil, nil
}
// isHTTPFuncOrMethodOnClient checks whether the given call expression is on
// either a function of the net/http package or a method of http.Client that
// returns (*http.Response, error).
func isHTTPFuncOrMethodOnClient(info *types.Info, expr *ast.CallExpr) bool {
fun, _ := expr.Fun.(*ast.SelectorExpr)
sig, _ := info.Types[fun].Type.(*types.Signature)
if sig == nil {
return false // the call is not of the form x.f()
}
res := sig.Results()
if res.Len() != 2 {
return false // the function called does not return two values.
}
isPtr, named := typesinternal.ReceiverNamed(res.At(0))
if !isPtr || named == nil || !typesinternal.IsTypeNamed(named, "net/http", "Response") {
return false // the first return type is not *http.Response.
}
errorType := types.Universe.Lookup("error").Type()
if !types.Identical(res.At(1).Type(), errorType) {
return false // the second return type is not error
}
typ := info.Types[fun.X].Type
if typ == nil {
id, ok := fun.X.(*ast.Ident)
return ok && id.Name == "http" // function in net/http package.
}
if typesinternal.IsTypeNamed(typ, "net/http", "Client") {
return true // method on http.Client.
}
ptr, ok := types.Unalias(typ).(*types.Pointer)
return ok && typesinternal.IsTypeNamed(ptr.Elem(), "net/http", "Client") // method on *http.Client.
}
// restOfBlock, given a traversal stack, finds the innermost containing
// block and returns the suffix of its statements starting with the current
// node, along with the number of call expressions encountered.
func restOfBlock(stack []ast.Node) ([]ast.Stmt, int) {
var ncalls int
for i := len(stack) - 1; i >= 0; i-- {
if b, ok := stack[i].(*ast.BlockStmt); ok {
for j, v := range b.List {
if v == stack[i+1] {
return b.List[j:], ncalls
}
}
break
}
if _, ok := stack[i].(*ast.CallExpr); ok {
ncalls++
}
}
return nil, 0
}
// rootIdent finds the root identifier x in a chain of selections x.y.z, or nil if not found.
func rootIdent(n ast.Node) *ast.Ident {
switch n := n.(type) {
case *ast.SelectorExpr:
return rootIdent(n.X)
case *ast.Ident:
return n
default:
return nil
}
}
================================================
FILE: go/analysis/passes/httpresponse/httpresponse_test.go
================================================
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package httpresponse_test
import (
"testing"
"golang.org/x/tools/go/analysis/analysistest"
"golang.org/x/tools/go/analysis/passes/httpresponse"
)
func Test(t *testing.T) {
testdata := analysistest.TestData()
analysistest.Run(t, testdata, httpresponse.Analyzer, "a", "typeparams")
}
================================================
FILE: go/analysis/passes/httpresponse/testdata/src/a/a.go
================================================
package a
import (
"log"
"net/http"
)
func goodHTTPGet() {
res, err := http.Get("http://foo.com")
if err != nil {
log.Fatal(err)
}
defer res.Body.Close()
}
func badHTTPGet() {
res, err := http.Get("http://foo.com")
defer res.Body.Close() // want "using res before checking for errors"
if err != nil {
log.Fatal(err)
}
}
func badHTTPHead() {
res, err := http.Head("http://foo.com")
defer res.Body.Close() // want "using res before checking for errors"
if err != nil {
log.Fatal(err)
}
}
func goodClientGet() {
client := http.DefaultClient
res, err := client.Get("http://foo.com")
if err != nil {
log.Fatal(err)
}
defer res.Body.Close()
}
func badClientPtrGet() {
client := http.DefaultClient
resp, err := client.Get("http://foo.com")
defer resp.Body.Close() // want "using resp before checking for errors"
if err != nil {
log.Fatal(err)
}
}
func badClientGet() {
client := http.Client{}
resp, err := client.Get("http://foo.com")
defer resp.Body.Close() // want "using resp before checking for errors"
if err != nil {
log.Fatal(err)
}
}
func badClientPtrDo() {
client := http.DefaultClient
req, err := http.NewRequest("GET", "http://foo.com", nil)
if err != nil {
log.Fatal(err)
}
resp, err := client.Do(req)
defer resp.Body.Close() // want "using resp before checking for errors"
if err != nil {
log.Fatal(err)
}
}
func badClientDo() {
var client http.Client
req, err := http.NewRequest("GET", "http://foo.com", nil)
if err != nil {
log.Fatal(err)
}
resp, err := client.Do(req)
defer resp.Body.Close() // want "using resp before checking for errors"
if err != nil {
log.Fatal(err)
}
}
func goodUnwrapResp() {
unwrapResp := func(resp *http.Response, err error) *http.Response {
if err != nil {
panic(err)
}
return resp
}
resp := unwrapResp(http.Get("https://golang.org"))
// It is ok to call defer here immediately as err has
// been checked in unwrapResp (see #52661).
defer resp.Body.Close()
}
func badUnwrapResp() {
unwrapResp := func(resp *http.Response, err error) string {
if err != nil {
panic(err)
}
return "https://golang.org/" + resp.Status
}
resp, err := http.Get(unwrapResp(http.Get("https://golang.org")))
defer resp.Body.Close() // want "using resp before checking for errors"
if err != nil {
log.Fatal(err)
}
}
type i66259 struct{}
func (_ *i66259) Foo() (*int, int) { return nil, 1 }
func issue66259() {
var f *i66259
f.Foo()
}
================================================
FILE: go/analysis/passes/httpresponse/testdata/src/typeparams/typeparams.go
================================================
// Copyright 2021 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// This file contains tests for the httpresponse checker.
package typeparams
import (
"log"
"net/http"
)
func badHTTPGet[T any](url string) {
res, err := http.Get(url)
defer res.Body.Close() // want "using res before checking for errors"
if err != nil {
log.Fatal(err)
}
}
func mkClient[T any]() *T {
return nil
}
func badClientHTTPGet() {
client := mkClient[http.Client]()
res, _ := client.Get("")
defer res.Body.Close() // want "using res before checking for errors"
}
// User-defined type embedded "http.Client"
type S[P any] struct {
http.Client
}
func unmatchedClientTypeName(client S[string]) {
res, _ := client.Get("")
defer res.Body.Close() // the name of client's type doesn't match "*http.Client"
}
// User-defined Client type
type C[P any] interface {
Get(url string) (resp *P, err error)
}
func userDefinedClientType(client C[http.Response]) {
resp, _ := client.Get("http://foo.com")
defer resp.Body.Close() // "client" is not of type "*http.Client"
}
================================================
FILE: go/analysis/passes/ifaceassert/cmd/ifaceassert/main.go
================================================
// Copyright 2020 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// The ifaceassert command runs the ifaceassert analyzer.
package main
import (
"golang.org/x/tools/go/analysis/passes/ifaceassert"
"golang.org/x/tools/go/analysis/singlechecker"
)
func main() { singlechecker.Main(ifaceassert.Analyzer) }
================================================
FILE: go/analysis/passes/ifaceassert/doc.go
================================================
// Copyright 2023 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package ifaceassert defines an Analyzer that flags
// impossible interface-interface type assertions.
//
// # Analyzer ifaceassert
//
// ifaceassert: detect impossible interface-to-interface type assertions
//
// This checker flags type assertions v.(T) and corresponding type-switch cases
// in which the static type V of v is an interface that cannot possibly implement
// the target interface T. This occurs when V and T contain methods with the same
// name but different signatures. Example:
//
// var v interface {
// Read()
// }
// _ = v.(io.Reader)
//
// The Read method in v has a different signature than the Read method in
// io.Reader, so this assertion cannot succeed.
package ifaceassert
================================================
FILE: go/analysis/passes/ifaceassert/ifaceassert.go
================================================
// Copyright 2020 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package ifaceassert
import (
_ "embed"
"go/ast"
"go/types"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/ast/inspector"
"golang.org/x/tools/internal/analysis/analyzerutil"
"golang.org/x/tools/internal/typeparams"
)
//go:embed doc.go
var doc string
var Analyzer = &analysis.Analyzer{
Name: "ifaceassert",
Doc: analyzerutil.MustExtractDoc(doc, "ifaceassert"),
URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/ifaceassert",
Requires: []*analysis.Analyzer{inspect.Analyzer},
Run: run,
}
// assertableTo checks whether interface v can be asserted into t. It returns
// nil on success, or the first conflicting method on failure.
func assertableTo(free *typeparams.Free, v, t types.Type) *types.Func {
if t == nil || v == nil {
// not assertable to, but there is no missing method
return nil
}
// ensure that v and t are interfaces
V, _ := v.Underlying().(*types.Interface)
T, _ := t.Underlying().(*types.Interface)
if V == nil || T == nil {
return nil
}
// Mitigations for interface comparisons and generics.
// TODO(https://github.com/golang/go/issues/50658): Support more precise conclusion.
if free.Has(V) || free.Has(T) {
return nil
}
if f, wrongType := types.MissingMethod(V, T, false); wrongType {
return f
}
return nil
}
func run(pass *analysis.Pass) (any, error) {
inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
nodeFilter := []ast.Node{
(*ast.TypeAssertExpr)(nil),
(*ast.TypeSwitchStmt)(nil),
}
var free typeparams.Free
inspect.Preorder(nodeFilter, func(n ast.Node) {
var (
assert *ast.TypeAssertExpr // v.(T) expression
targets []ast.Expr // interfaces T in v.(T)
)
switch n := n.(type) {
case *ast.TypeAssertExpr:
// take care of v.(type) in *ast.TypeSwitchStmt
if n.Type == nil {
return
}
assert = n
targets = append(targets, n.Type)
case *ast.TypeSwitchStmt:
// retrieve type assertion from type switch's 'assign' field
switch t := n.Assign.(type) {
case *ast.ExprStmt:
assert = t.X.(*ast.TypeAssertExpr)
case *ast.AssignStmt:
assert = t.Rhs[0].(*ast.TypeAssertExpr)
}
// gather target types from case clauses
for _, c := range n.Body.List {
targets = append(targets, c.(*ast.CaseClause).List...)
}
}
V := pass.TypesInfo.TypeOf(assert.X)
for _, target := range targets {
T := pass.TypesInfo.TypeOf(target)
if f := assertableTo(&free, V, T); f != nil {
pass.Reportf(
target.Pos(),
"impossible type assertion: no type can implement both %v and %v (conflicting types for %v method)",
V, T, f.Name(),
)
}
}
})
return nil, nil
}
================================================
FILE: go/analysis/passes/ifaceassert/ifaceassert_test.go
================================================
// Copyright 2020 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package ifaceassert_test
import (
"testing"
"golang.org/x/tools/go/analysis/analysistest"
"golang.org/x/tools/go/analysis/passes/ifaceassert"
)
func Test(t *testing.T) {
testdata := analysistest.TestData()
analysistest.Run(t, testdata, ifaceassert.Analyzer, "a", "typeparams")
}
================================================
FILE: go/analysis/passes/ifaceassert/testdata/src/a/a.go
================================================
// Copyright 2020 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// This file contains tests for the ifaceassert checker.
package a
import "io"
func InterfaceAssertionTest() {
var (
a io.ReadWriteSeeker
b interface {
Read()
Write()
}
)
_ = a.(io.Reader)
_ = a.(io.ReadWriter)
_ = b.(io.Reader) // want `^impossible type assertion: no type can implement both interface{Read\(\); Write\(\)} and io.Reader \(conflicting types for Read method\)$`
_ = b.(interface { // want `^impossible type assertion: no type can implement both interface{Read\(\); Write\(\)} and interface{Read\(p \[\]byte\) \(n int, err error\)} \(conflicting types for Read method\)$`
Read(p []byte) (n int, err error)
})
switch a.(type) {
case io.ReadWriter:
case interface { // want `^impossible type assertion: no type can implement both io.ReadWriteSeeker and interface{Write\(\)} \(conflicting types for Write method\)$`
Write()
}:
default:
}
switch b := b.(type) {
case io.ReadWriter, interface{ Read() }: // want `^impossible type assertion: no type can implement both interface{Read\(\); Write\(\)} and io.ReadWriter \(conflicting types for Read method\)$`
case io.Writer: // want `^impossible type assertion: no type can implement both interface{Read\(\); Write\(\)} and io.Writer \(conflicting types for Write method\)$`
default:
_ = b
}
}
================================================
FILE: go/analysis/passes/ifaceassert/testdata/src/typeparams/typeparams.go
================================================
// Copyright 2021 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package typeparams
import "io"
type SourceReader[Source any] interface {
Read(p Source) (n int, err error)
}
func GenericInterfaceAssertionTest[T io.Reader]() {
var (
a SourceReader[[]byte]
b SourceReader[[]int]
r io.Reader
)
_ = a.(io.Reader)
_ = b.(io.Reader) // want `^impossible type assertion: no type can implement both typeparams.SourceReader\[\[\]int\] and io.Reader \(conflicting types for Read method\)$`
_ = r.(SourceReader[[]byte])
_ = r.(SourceReader[[]int]) // want `^impossible type assertion: no type can implement both io.Reader and typeparams.SourceReader\[\[\]int\] \(conflicting types for Read method\)$`
_ = r.(T) // not actually an iface assertion, so checked by the type checker.
switch a.(type) {
case io.Reader:
default:
}
switch b.(type) {
case io.Reader: // want `^impossible type assertion: no type can implement both typeparams.SourceReader\[\[\]int\] and io.Reader \(conflicting types for Read method\)$`
default:
}
}
// Issue 50658: Check for type parameters in type switches.
type Float interface {
float32 | float64
}
type Doer[F Float] interface {
Do() F
}
func Underlying[F Float](v Doer[F]) string {
switch v.(type) {
case Doer[float32]:
return "float32!"
case Doer[float64]:
return "float64!"
default:
return ""
}
}
func DoIf[F Float]() {
// This is a synthetic function to create a non-generic to generic assignment.
// This function does not make much sense.
var v Doer[float32]
if t, ok := v.(Doer[F]); ok {
t.Do()
}
}
func IsASwitch[F Float, U Float](v Doer[F]) bool {
switch v.(type) {
case Doer[U]:
return true
}
return false
}
func IsA[F Float, U Float](v Doer[F]) bool {
_, is := v.(Doer[U])
return is
}
func LayeredTypes[F Float]() {
// This is a synthetic function cover more isParameterized cases.
type T interface {
foo() struct{ _ map[T][2]chan *F }
}
type V interface {
foo() struct{ _ map[T][2]chan *float32 }
}
var t T
var v V
t, _ = v.(T)
_ = t
}
type X[T any] struct{}
func (x X[T]) m(T) {}
func InstancesOfGenericMethods() {
var x interface{ m(string) }
// _ = x.(X[int]) // BAD. Not enabled as it does not type check.
_ = x.(X[string]) // OK
}
================================================
FILE: go/analysis/passes/inline/cmd/inline/main.go
================================================
// Copyright 2025 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// The inline command applies the inliner to the specified packages of
// Go source code. Run this command to report all fixes:
//
// $ go run ./go/analysis/passes/inline/cmd/inline packages...
//
// Run this command to preview the changes:
//
// $ go run ./go/analysis/passes/inline/cmd/inline -fix -diff packages...
//
// And run this command to apply them:
//
// $ go run ./go/analysis/passes/inline/cmd/inline -fix packages...
//
// This internal command is not officially supported. In the long
// term, we plan to migrate this functionality into "go fix"; see Go
// issues https//go.dev/issue/32816, 71859, 73605.
package main
import (
"golang.org/x/tools/go/analysis/passes/inline"
"golang.org/x/tools/go/analysis/singlechecker"
)
func main() { singlechecker.Main(inline.Analyzer) }
================================================
FILE: go/analysis/passes/inline/doc.go
================================================
// Copyright 2025 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
/*
Package inline defines an analyzer that inlines calls to functions
and uses of constants marked with a "//go:fix inline" directive.
# Analyzer inline
inline: apply fixes based on 'go:fix inline' comment directives
The inline analyzer inlines functions and constants that are marked for inlining.
## Functions
Given a function that is marked for inlining, like this one:
//go:fix inline
func Square(x int) int { return Pow(x, 2) }
this analyzer will recommend that calls to the function elsewhere, in the same
or other packages, should be inlined.
Inlining can be used to move off of a deprecated function:
// Deprecated: prefer Pow(x, 2).
//go:fix inline
func Square(x int) int { return Pow(x, 2) }
It can also be used to move off of an obsolete package,
as when the import path has changed or a higher major version is available:
package pkg
import pkg2 "pkg/v2"
//go:fix inline
func F() { pkg2.F(nil) }
Replacing a call pkg.F() by pkg2.F(nil) can have no effect on the program,
so this mechanism provides a low-risk way to update large numbers of calls.
We recommend, where possible, expressing the old API in terms of the new one
to enable automatic migration.
The inliner takes care to avoid behavior changes, even subtle ones,
such as changes to the order in which argument expressions are
evaluated. When it cannot safely eliminate all parameter variables,
it may introduce a "binding declaration" of the form
var params = args
to evaluate argument expressions in the correct order and bind them to
parameter variables. Since the resulting code transformation may be
stylistically suboptimal, such inlinings may be disabled by specifying
the -inline.allow_binding_decl=false flag to the analyzer driver.
(In cases where it is not safe to "reduce" a call—that is, to replace
a call f(x) by the body of function f, suitably substituted—the
inliner machinery is capable of replacing f by a function literal,
func(){...}(). However, the inline analyzer discards all such
"literalizations" unconditionally, again on grounds of style.)
## Constants
Given a constant that is marked for inlining, like this one:
//go:fix inline
const Ptr = Pointer
this analyzer will recommend that uses of Ptr should be replaced with Pointer.
As with functions, inlining can be used to replace deprecated constants and
constants in obsolete packages.
A constant definition can be marked for inlining only if it refers to another
named constant.
The "//go:fix inline" comment must appear before a single const declaration on its own,
as above; before a const declaration that is part of a group, as in this case:
const (
C = 1
//go:fix inline
Ptr = Pointer
)
or before a group, applying to every constant in the group:
//go:fix inline
const (
Ptr = Pointer
Val = Value
)
The proposal https://go.dev/issue/32816 introduces the "//go:fix inline" directives.
You can use this command to apply inline fixes en masse:
$ go run golang.org/x/tools/go/analysis/passes/inline/cmd/inline@latest -fix ./...
# Analyzer gofixdirective
gofixdirective: validate uses of //go:fix comment directives
The gofixdirective analyzer checks "//go:fix inline" directives for correctness.
See the documentation for the gofix analyzer for more about "/go:fix inline".
*/
package inline
================================================
FILE: go/analysis/passes/inline/inline.go
================================================
// Copyright 2023 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package inline
import (
"fmt"
"go/ast"
"go/types"
"slices"
"strings"
_ "embed"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/analysis/passes/internal/gofixdirective"
"golang.org/x/tools/go/ast/edge"
"golang.org/x/tools/go/ast/inspector"
"golang.org/x/tools/go/types/typeutil"
"golang.org/x/tools/internal/analysis/analyzerutil"
typeindexanalyzer "golang.org/x/tools/internal/analysis/typeindex"
"golang.org/x/tools/internal/astutil"
"golang.org/x/tools/internal/moreiters"
"golang.org/x/tools/internal/packagepath"
"golang.org/x/tools/internal/refactor"
"golang.org/x/tools/internal/refactor/inline"
"golang.org/x/tools/internal/typesinternal"
"golang.org/x/tools/internal/typesinternal/typeindex"
)
//go:embed doc.go
var doc string
var Analyzer = &analysis.Analyzer{
Name: "inline",
Doc: analyzerutil.MustExtractDoc(doc, "inline"),
URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/inline",
Run: run,
FactTypes: []analysis.Fact{
(*goFixInlineFuncFact)(nil),
(*goFixInlineConstFact)(nil),
(*goFixInlineAliasFact)(nil),
},
Requires: []*analysis.Analyzer{
inspect.Analyzer,
typeindexanalyzer.Analyzer,
},
}
var (
allowBindingDecl bool
lazyEdits bool
)
func init() {
Analyzer.Flags.BoolVar(&allowBindingDecl, "allow_binding_decl", false,
"permit inlinings that require a 'var params = args' declaration")
Analyzer.Flags.BoolVar(&lazyEdits, "lazy_edits", false,
"compute edits lazily (only meaningful to gopls driver)")
}
// analyzer holds the state for this analysis.
type analyzer struct {
pass *analysis.Pass
root inspector.Cursor
index *typeindex.Index
// memoization of repeated calls for same file.
fileContent map[string][]byte
// memoization of fact imports (nil => no fact)
inlinableFuncs map[*types.Func]*inline.Callee
inlinableConsts map[*types.Const]*goFixInlineConstFact
inlinableAliases map[*types.TypeName]*goFixInlineAliasFact
}
func run(pass *analysis.Pass) (any, error) {
a := &analyzer{
pass: pass,
root: pass.ResultOf[inspect.Analyzer].(*inspector.Inspector).Root(),
index: pass.ResultOf[typeindexanalyzer.Analyzer].(*typeindex.Index),
fileContent: make(map[string][]byte),
inlinableFuncs: make(map[*types.Func]*inline.Callee),
inlinableConsts: make(map[*types.Const]*goFixInlineConstFact),
inlinableAliases: make(map[*types.TypeName]*goFixInlineAliasFact),
}
gofixdirective.Find(pass, a.root, a)
a.inline()
return nil, nil
}
// HandleFunc exports a fact for functions marked with go:fix.
func (a *analyzer) HandleFunc(decl *ast.FuncDecl) {
content, err := a.readFile(decl)
if err != nil {
a.pass.Reportf(decl.Doc.Pos(), "invalid inlining candidate: cannot read source file: %v", err)
return
}
callee, err := inline.AnalyzeCallee(discard, a.pass.Fset, a.pass.Pkg, a.pass.TypesInfo, decl, content)
if err != nil {
a.pass.Reportf(decl.Doc.Pos(), "invalid inlining candidate: %v", err)
return
}
fn := a.pass.TypesInfo.Defs[decl.Name].(*types.Func)
a.pass.ExportObjectFact(fn, &goFixInlineFuncFact{callee})
a.inlinableFuncs[fn] = callee
}
// HandleAlias exports a fact for aliases marked with go:fix.
func (a *analyzer) HandleAlias(spec *ast.TypeSpec) {
// Remember that this is an inlinable alias.
typ := &goFixInlineAliasFact{}
lhs := a.pass.TypesInfo.Defs[spec.Name].(*types.TypeName)
a.inlinableAliases[lhs] = typ
// Create a fact only if the LHS is exported and defined at top level.
// We create a fact even if the RHS is non-exported,
// so we can warn about uses in other packages.
if lhs.Exported() && typesinternal.IsPackageLevel(lhs) {
a.pass.ExportObjectFact(lhs, typ)
}
}
// HandleConst exports a fact for constants marked with go:fix.
func (a *analyzer) HandleConst(nameIdent, rhsIdent *ast.Ident) {
lhs := a.pass.TypesInfo.Defs[nameIdent].(*types.Const)
rhs := a.pass.TypesInfo.Uses[rhsIdent].(*types.Const) // must be so in a well-typed program
con := &goFixInlineConstFact{
RHSName: rhs.Name(),
RHSPkgName: rhs.Pkg().Name(),
RHSPkgPath: rhs.Pkg().Path(),
}
if rhs.Pkg() == a.pass.Pkg {
con.rhsObj = rhs
}
a.inlinableConsts[lhs] = con
// Create a fact only if the LHS is exported and defined at top level.
// We create a fact even if the RHS is non-exported,
// so we can warn about uses in other packages.
if lhs.Exported() && typesinternal.IsPackageLevel(lhs) {
a.pass.ExportObjectFact(lhs, con)
}
}
// inline inlines each static call to an inlinable function
// and each reference to an inlinable constant or type alias.
func (a *analyzer) inline() {
for cur := range a.root.Preorder((*ast.CallExpr)(nil), (*ast.Ident)(nil)) {
switch n := cur.Node().(type) {
case *ast.CallExpr:
a.inlineCall(n, cur)
case *ast.Ident:
switch t := a.pass.TypesInfo.Uses[n].(type) {
case *types.TypeName:
a.inlineAlias(t, cur)
case *types.Const:
a.inlineConst(t, cur)
}
}
}
}
// If call is a call to an inlinable func, suggest inlining its use at cur.
func (a *analyzer) inlineCall(call *ast.CallExpr, cur inspector.Cursor) {
if fn := typeutil.StaticCallee(a.pass.TypesInfo, call); fn != nil {
// Inlinable?
callee, ok := a.inlinableFuncs[fn]
if !ok {
var fact goFixInlineFuncFact
if a.pass.ImportObjectFact(fn, &fact) {
callee = fact.Callee
a.inlinableFuncs[fn] = callee
}
}
if callee == nil {
return // nope
}
if a.withinTestOf(cur, fn) {
return // don't inline a function from within its own test
}
// Compute the edits.
//
// Ordinarily the analyzer reports a fix containing
// edits. However, the algorithm is somewhat expensive
// (unnecessarily so: see go.dev/issue/75773) so
// to reduce costs in gopls, we omit the edits,
// meaning that gopls must compute them on demand
// (based on the Diagnostic.Category) when they are
// requested via a code action.
//
// This does mean that the following categories of
// caller-dependent obstacles to inlining will be
// reported when the gopls user requests the fix,
// rather than by quietly suppressing the diagnostic:
// - shadowing problems
// - callee imports inaccessible "internal" packages
// - callee refers to nonexported symbols
// - callee uses too-new Go features
// - inlining call from a cgo file
var edits []analysis.TextEdit
if !lazyEdits {
// Inline the call.
caller := &inline.Caller{
Fset: a.pass.Fset,
Types: a.pass.Pkg,
Info: a.pass.TypesInfo,
File: astutil.EnclosingFile(cur),
Call: call,
CountUses: func(pkgname *types.PkgName) int {
return moreiters.Len(a.index.Uses(pkgname))
},
}
res, err := inline.Inline(caller, callee, &inline.Options{Logf: discard})
if err != nil {
a.pass.Reportf(call.Lparen, "%v", err)
return
}
if res.Literalized {
// Users are not fond of inlinings that literalize
// f(x) to func() { ... }(), so avoid them.
//
// (Unfortunately the inliner is very timid,
// and often literalizes when it cannot prove that
// reducing the call is safe; the user of this tool
// has no indication of what the problem is.)
return
}
if res.BindingDecl && !allowBindingDecl {
// When applying fix en masse, users are similarly
// unenthusiastic about inlinings that cannot
// entirely eliminate the parameters and
// insert a 'var params = args' declaration.
// The flag allows them to decline such fixes.
return
}
edits = res.Edits
}
a.pass.Report(analysis.Diagnostic{
Pos: call.Pos(),
End: call.End(),
Message: fmt.Sprintf("Call of %v should be inlined", callee),
Category: "inline_call", // keep consistent with gopls/internal/golang.fixInlineCall
SuggestedFixes: []analysis.SuggestedFix{{
Message: fmt.Sprintf("Inline call of %v", callee),
TextEdits: edits, // within gopls, this is nil => compute fix's edits lazily
}},
})
}
}
// withinTestOf reports whether cur is within a dedicated test
// function for the inlinable target function.
// A call within its dedicated test should not be inlined.
func (a *analyzer) withinTestOf(cur inspector.Cursor, target *types.Func) bool {
curFuncDecl, ok := moreiters.First(cur.Enclosing((*ast.FuncDecl)(nil)))
if !ok {
return false // not in a function
}
funcDecl := curFuncDecl.Node().(*ast.FuncDecl)
if funcDecl.Recv != nil {
return false // not a test func
}
if strings.TrimSuffix(a.pass.Pkg.Path(), "_test") != target.Pkg().Path() {
return false // different package
}
if !strings.HasSuffix(a.pass.Fset.File(funcDecl.Pos()).Name(), "_test.go") {
return false // not a test file
}
// Computed expected SYMBOL portion of "TestSYMBOL_comment"
// for the target symbol.
symbol := target.Name()
if recv := target.Signature().Recv(); recv != nil {
_, named := typesinternal.ReceiverNamed(recv)
symbol = named.Obj().Name() + "_" + symbol
}
// TODO(adonovan): use a proper Test function parser.
fname := funcDecl.Name.Name
for _, pre := range []string{"Test", "Example", "Bench"} {
if fname == pre+symbol || strings.HasPrefix(fname, pre+symbol+"_") {
return true
}
}
return false
}
// If tn is the TypeName of an inlinable alias, suggest inlining its use at cur.
func (a *analyzer) inlineAlias(tn *types.TypeName, curId inspector.Cursor) {
inalias, ok := a.inlinableAliases[tn]
if !ok {
var fact goFixInlineAliasFact
if a.pass.ImportObjectFact(tn, &fact) {
inalias = &fact
a.inlinableAliases[tn] = inalias
}
}
if inalias == nil {
return // nope
}
alias := tn.Type().(*types.Alias)
// Remember the names of the alias's type params. When we check for shadowing
// later, we'll ignore these because they won't appear in the replacement text.
typeParamNames := map[*types.TypeName]bool{}
for tp := range alias.TypeParams().TypeParams() {
typeParamNames[tp.Obj()] = true
}
rhs := alias.Rhs()
curPath := a.pass.Pkg.Path()
curFile := astutil.EnclosingFile(curId)
id := curId.Node().(*ast.Ident)
// Find the complete identifier, which may take any of these forms:
// Id
// Id[T]
// Id[K, V]
// pkg.Id
// pkg.Id[T]
// pkg.Id[K, V]
var expr ast.Expr = id
if curId.ParentEdgeKind() == edge.SelectorExpr_Sel {
curId = curId.Parent()
expr = curId.Node().(ast.Expr)
}
// If expr is part of an IndexExpr or IndexListExpr, we'll need that node.
// Given C[int], TypeOf(C) is generic but TypeOf(C[int]) is instantiated.
switch curId.ParentEdgeKind() {
case edge.IndexExpr_X:
expr = curId.Parent().Node().(*ast.IndexExpr)
case edge.IndexListExpr_X:
expr = curId.Parent().Node().(*ast.IndexListExpr)
}
t := a.pass.TypesInfo.TypeOf(expr).(*types.Alias) // type of entire identifier
if targs := t.TypeArgs(); targs.Len() > 0 {
// Instantiate the alias with the type args from this use.
// For example, given type A = M[K, V], compute the type of the use
// A[int, Foo] as M[int, Foo].
// Don't validate instantiation: it can't panic unless we have a bug,
// in which case seeing the stack trace via telemetry would be helpful.
instAlias, _ := types.Instantiate(nil, alias, slices.Collect(targs.Types()), false)
rhs = instAlias.(*types.Alias).Rhs()
}
// We have an identifier A here (n), possibly qualified by a package
// identifier (sel.n), and an inlinable "type A = rhs" elsewhere.
//
// We can replace A with rhs if no name in rhs is shadowed at n's position,
// and every package in rhs is importable by the current package.
var (
importPrefixes = map[string]string{curPath: ""} // from pkg path to prefix
edits []analysis.TextEdit
)
for _, tn := range typenames(rhs) {
// Ignore the type parameters of the alias: they won't appear in the result.
if typeParamNames[tn] {
continue
}
var pkgPath, pkgName string
if pkg := tn.Pkg(); pkg != nil {
pkgPath = pkg.Path()
pkgName = pkg.Name()
}
if pkgPath == "" || pkgPath == curPath {
// The name is in the current package or the universe scope, so no import
// is required. Check that it is not shadowed (that is, that the type
// it refers to in rhs is the same one it refers to at n).
scope := a.pass.TypesInfo.Scopes[curFile].Innermost(id.Pos()) // n's scope
_, obj := scope.LookupParent(tn.Name(), id.Pos()) // what qn.name means in n's scope
if obj != tn {
return
}
} else if !packagepath.CanImport(a.pass.Pkg.Path(), pkgPath) {
// If this package can't see the package of this part of rhs, we can't inline.
return
} else if _, ok := importPrefixes[pkgPath]; !ok {
// Use AddImport to add pkgPath if it's not there already. Associate the prefix it assigns
// with the prefix it assigns
// with the package path for use by the TypeString qualifier below.
prefix, eds := refactor.AddImport(
a.pass.TypesInfo, curFile, pkgName, pkgPath, tn.Name(), id.Pos())
importPrefixes[pkgPath] = strings.TrimSuffix(prefix, ".")
edits = append(edits, eds...)
}
}
// To get the replacement text, render the alias RHS using the package prefixes
// we assigned above.
newText := types.TypeString(rhs, func(p *types.Package) string {
if p == a.pass.Pkg {
return ""
}
if prefix, ok := importPrefixes[p.Path()]; ok {
return prefix
}
panic(fmt.Sprintf("in %q, package path %q has no import prefix", rhs, p.Path()))
})
a.reportInline("type alias", "Type alias", expr, edits, newText)
}
// typenames returns the TypeNames for types within t (including t itself) that have
// them: basic types, named types and alias types.
// The same name may appear more than once.
func typenames(t types.Type) []*types.TypeName {
var tns []*types.TypeName
var visit func(types.Type)
visit = func(t types.Type) {
if hasName, ok := t.(interface{ Obj() *types.TypeName }); ok {
tns = append(tns, hasName.Obj())
}
switch t := t.(type) {
case *types.Basic:
tns = append(tns, types.Universe.Lookup(t.Name()).(*types.TypeName))
case *types.Named:
for t := range t.TypeArgs().Types() {
visit(t)
}
case *types.Alias:
for t := range t.TypeArgs().Types() {
visit(t)
}
case *types.TypeParam:
tns = append(tns, t.Obj())
case *types.Pointer:
visit(t.Elem())
case *types.Slice:
visit(t.Elem())
case *types.Array:
visit(t.Elem())
case *types.Chan:
visit(t.Elem())
case *types.Map:
visit(t.Key())
visit(t.Elem())
case *types.Struct:
for field := range t.Fields() {
visit(field.Type())
}
case *types.Signature:
// Ignore the receiver: although it may be present, it has no meaning
// in a type expression.
// Ditto for receiver type params.
// Also, function type params cannot appear in a type expression.
if t.TypeParams() != nil {
panic("Signature.TypeParams in type expression")
}
visit(t.Params())
visit(t.Results())
case *types.Interface:
for etyp := range t.EmbeddedTypes() {
visit(etyp)
}
for method := range t.ExplicitMethods() {
visit(method.Type())
}
case *types.Tuple:
for v := range t.Variables() {
visit(v.Type())
}
case *types.Union:
panic("Union in type expression")
default:
panic(fmt.Sprintf("unknown type %T", t))
}
}
visit(t)
return tns
}
// If con is an inlinable constant, suggest inlining its use at cur.
func (a *analyzer) inlineConst(con *types.Const, cur inspector.Cursor) {
incon, ok := a.inlinableConsts[con]
if !ok {
var fact goFixInlineConstFact
if a.pass.ImportObjectFact(con, &fact) {
incon = &fact
a.inlinableConsts[con] = incon
}
}
if incon == nil {
return // nope
}
// If n is qualified by a package identifier, we'll need the full selector expression.
curFile := astutil.EnclosingFile(cur)
n := cur.Node().(*ast.Ident)
// We have an identifier A here (n), possibly qualified by a package identifier (sel.X,
// where sel is the parent of n), // and an inlinable "const A = B" elsewhere (incon).
// Consider replacing A with B.
// Check that the expression we are inlining (B) means the same thing
// (refers to the same object) in n's scope as it does in A's scope.
// If the RHS is not in the current package, AddImport will handle
// shadowing, so we only need to worry about when both expressions
// are in the current package.
if a.pass.Pkg.Path() == incon.RHSPkgPath {
// incon.rhsObj is the object referred to by B in the definition of A.
scope := a.pass.TypesInfo.Scopes[curFile].Innermost(n.Pos()) // n's scope
_, obj := scope.LookupParent(incon.RHSName, n.Pos()) // what "B" means in n's scope
if obj == nil {
// Should be impossible: if code at n can refer to the LHS,
// it can refer to the RHS.
panic(fmt.Sprintf("no object for inlinable const %s RHS %s", n.Name, incon.RHSName))
}
if obj != incon.rhsObj {
// "B" means something different here than at the inlinable const's scope.
return
}
} else if !packagepath.CanImport(a.pass.Pkg.Path(), incon.RHSPkgPath) {
// If this package can't see the RHS's package, we can't inline.
return
}
var (
importPrefix string
edits []analysis.TextEdit
)
if incon.RHSPkgPath != a.pass.Pkg.Path() {
importPrefix, edits = refactor.AddImport(
a.pass.TypesInfo, curFile, incon.RHSPkgName, incon.RHSPkgPath, incon.RHSName, n.Pos())
}
// If n is qualified by a package identifier, we'll need the full selector expression.
var expr ast.Expr = n
if cur.ParentEdgeKind() == edge.SelectorExpr_Sel {
expr = cur.Parent().Node().(ast.Expr)
}
a.reportInline("constant", "Constant", expr, edits, importPrefix+incon.RHSName)
}
// reportInline reports a diagnostic for fixing an inlinable name.
func (a *analyzer) reportInline(kind, capKind string, ident ast.Expr, edits []analysis.TextEdit, newText string) {
edits = append(edits, analysis.TextEdit{
Pos: ident.Pos(),
End: ident.End(),
NewText: []byte(newText),
})
name := astutil.Format(a.pass.Fset, ident)
a.pass.Report(analysis.Diagnostic{
Pos: ident.Pos(),
End: ident.End(),
Message: fmt.Sprintf("%s %s should be inlined", capKind, name),
SuggestedFixes: []analysis.SuggestedFix{{
Message: fmt.Sprintf("Inline %s %s", kind, name),
TextEdits: edits,
}},
})
}
func (a *analyzer) readFile(node ast.Node) ([]byte, error) {
filename := a.pass.Fset.File(node.Pos()).Name()
content, ok := a.fileContent[filename]
if !ok {
var err error
content, err = a.pass.ReadFile(filename)
if err != nil {
return nil, err
}
a.fileContent[filename] = content
}
return content, nil
}
// A goFixInlineFuncFact is exported for each function marked "//go:fix inline".
// It holds information about the callee to support inlining.
type goFixInlineFuncFact struct{ Callee *inline.Callee }
func (f *goFixInlineFuncFact) String() string { return "goFixInline " + f.Callee.String() }
func (*goFixInlineFuncFact) AFact() {}
// A goFixInlineConstFact is exported for each constant marked "//go:fix inline".
// It holds information about an inlinable constant. Gob-serializable.
type goFixInlineConstFact struct {
// Information about "const LHSName = RHSName".
RHSName string
RHSPkgPath string
RHSPkgName string
rhsObj types.Object // for current package
}
func (c *goFixInlineConstFact) String() string {
return fmt.Sprintf("goFixInline const %q.%s", c.RHSPkgPath, c.RHSName)
}
func (*goFixInlineConstFact) AFact() {}
// A goFixInlineAliasFact is exported for each type alias marked "//go:fix inline".
// It holds no information; its mere existence demonstrates that an alias is inlinable.
type goFixInlineAliasFact struct{}
func (c *goFixInlineAliasFact) String() string { return "goFixInline alias" }
func (*goFixInlineAliasFact) AFact() {}
func discard(string, ...any) {}
================================================
FILE: go/analysis/passes/inline/inline_test.go
================================================
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package inline
import (
"fmt"
"go/ast"
"go/importer"
"go/parser"
"go/token"
"go/types"
"slices"
"testing"
gocmp "github.com/google/go-cmp/cmp"
"golang.org/x/tools/go/analysis/analysistest"
"golang.org/x/tools/internal/testenv"
"golang.org/x/tools/internal/testfiles"
)
func TestAnalyzer(t *testing.T) {
if testenv.Go1Point() < 24 {
testenv.NeedsGoExperiment(t, "aliastypeparams")
}
analysistest.RunWithSuggestedFixes(t, analysistest.TestData(), Analyzer, "a", "b", "rmimport")
dir1 := testfiles.ExtractTxtarFileToTmp(t, "testdata/src/issue76190.txtar")
analysistest.RunWithSuggestedFixes(t, dir1, Analyzer, "example.com/a", "example.com/b")
dir2 := testfiles.ExtractTxtarFileToTmp(t, "testdata/src/issue77610.txtar")
analysistest.RunWithSuggestedFixes(t, dir2, Analyzer,
"example.com/a",
"example.com/b",
"example.com/c",
"example.com/d",
)
}
func TestAllowBindingDeclFlag(t *testing.T) {
saved := allowBindingDecl
defer func() { allowBindingDecl = saved }()
run := func(allow bool) {
name := fmt.Sprintf("binding_%v", allow)
t.Run(name, func(t *testing.T) {
allowBindingDecl = allow
analysistest.RunWithSuggestedFixes(t, analysistest.TestData(), Analyzer, name)
})
}
run(true) // testdata/src/binding_true
run(false) // testdata/src/binding_false
}
func TestTypesWithNames(t *testing.T) {
// Test setup inspired by internal/analysis/addimport_test.go.
testenv.NeedsDefaultImporter(t)
for _, test := range []struct {
typeExpr string
want []string
}{
{
"int",
[]string{"int"},
},
{
"*int",
[]string{"int"},
},
{
"[]*int",
[]string{"int"},
},
{
"[2]int",
[]string{"int"},
},
{
// go/types does not expose the length expression.
"[unsafe.Sizeof(uint(1))]int",
[]string{"int"},
},
{
"map[string]int",
[]string{"int", "string"},
},
{
"map[int]struct{x, y int}",
[]string{"int"},
},
{
"T",
[]string{"a.T"},
},
{
"iter.Seq[int]",
[]string{"int", "iter.Seq"},
},
{
"io.Reader",
[]string{"io.Reader"},
},
{
"map[*io.Writer]map[T]A",
[]string{"a.A", "a.T", "io.Writer"},
},
{
"func(int, int) (bool, error)",
[]string{"bool", "error", "int"},
},
{
"func(int, ...string) (T, *T, error)",
[]string{"a.T", "error", "int", "string"},
},
{
"func(iter.Seq[int])",
[]string{"int", "iter.Seq"},
},
{
"struct { a int; b bool}",
[]string{"bool", "int"},
},
{
"struct { io.Reader; a int}",
[]string{"int", "io.Reader"},
},
{
"map[*string]struct{x chan int; y [2]bool}",
[]string{"bool", "int", "string"},
},
{
"interface {F(int) bool}",
[]string{"bool", "int"},
},
{
"interface {io.Reader; F(int) bool}",
[]string{"bool", "int", "io.Reader"},
},
{
"G", // a type parameter of the function
[]string{"a.G"},
},
} {
src := `
package a
import ("io"; "iter"; "unsafe")
func _(io.Reader, iter.Seq[int]) uintptr {return unsafe.Sizeof(1)}
type T int
type A = T
func F[G any]() {
var V ` + test.typeExpr + `
_ = V
}`
// parse
fset := token.NewFileSet()
f, err := parser.ParseFile(fset, "a.go", src, 0)
if err != nil {
t.Errorf("%s: %v", test.typeExpr, err)
continue
}
// type-check
info := &types.Info{
Types: make(map[ast.Expr]types.TypeAndValue),
Scopes: make(map[ast.Node]*types.Scope),
Defs: make(map[*ast.Ident]types.Object),
Implicits: make(map[ast.Node]types.Object),
}
conf := &types.Config{
Error: func(err error) { t.Fatalf("%s: %v", test.typeExpr, err) },
Importer: importer.Default(),
}
pkg, err := conf.Check(f.Name.Name, fset, []*ast.File{f}, info)
if err != nil {
t.Errorf("%s: %v", test.typeExpr, err)
continue
}
// Look at V's type.
typ := pkg.Scope().Lookup("F").(*types.Func).
Scope().Lookup("V").(*types.Var).Type()
tns := typenames(typ)
// Sort names for comparison.
var got []string
for _, tn := range tns {
var prefix string
if p := tn.Pkg(); p != nil && p.Path() != "" {
prefix = p.Path() + "."
}
got = append(got, prefix+tn.Name())
}
slices.Sort(got)
got = slices.Compact(got)
if diff := gocmp.Diff(test.want, got); diff != "" {
t.Errorf("%s: mismatch (-want, +got):\n%s", test.typeExpr, diff)
}
}
}
================================================
FILE: go/analysis/passes/inline/issue77844_test.go
================================================
// Copyright 2025 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package inline
import (
"golang.org/x/tools/go/analysis/analysistest"
"golang.org/x/tools/internal/testfiles"
"testing"
)
func TestIssue77844(t *testing.T) {
dir := testfiles.ExtractTxtarFileToTmp(t, "testdata/src/issue77844.txtar")
analysistest.Run(t, dir, Analyzer, "example.com/main")
}
================================================
FILE: go/analysis/passes/inline/testdata/src/a/a.go
================================================
package a
import "a/internal"
// Functions.
func f() {
One() // want `Call of a.One should be inlined`
new(T).Two() // want `Call of \(a.T\).Two should be inlined`
}
type T struct{}
//go:fix inline
func One() int { return one } // want One:`goFixInline a.One`
const one = 1
//go:fix inline
func (T) Two() int { return 2 } // want Two:`goFixInline \(a.T\).Two`
// Constants.
const Uno = 1
//go:fix inline
const In1 = Uno // want In1: `goFixInline const "a".Uno`
const (
no1 = one
//go:fix inline
In2 = one // want In2: `goFixInline const "a".one`
)
//go:fix inline
const (
in3 = one
in4 = one
bad1 = 1 // want `invalid //go:fix inline directive: const value is not the name of another constant`
)
//go:fix inline
const in5,
in6,
bad2 = one, one,
one + 1 // want `invalid //go:fix inline directive: const value is not the name of another constant`
// Make sure we don't crash on iota consts, but still process the whole decl.
//
//go:fix inline
const (
a = iota // want `invalid //go:fix inline directive: const value is iota`
b
in7 = one
)
func _() {
x := In1 // want `Constant In1 should be inlined`
x = In2 // want `Constant In2 should be inlined`
x = in3 // want `Constant in3 should be inlined`
x = in4 // want `Constant in4 should be inlined`
x = in5 // want `Constant in5 should be inlined`
x = in6 // want `Constant in6 should be inlined`
x = in7 // want `Constant in7 should be inlined`
x = no1
_ = x
in1 := 1 // don't inline lvalues
_ = in1
}
const (
x = 1
//go:fix inline
in8 = x
)
//go:fix inline
const D = internal.D // want D: `goFixInline const "a/internal".D`
func shadow() {
var x int // shadows x at package scope
//go:fix inline
const a = iota // want `invalid //go:fix inline directive: const value is iota`
const iota = 2
// Below this point, iota is an ordinary constant.
//go:fix inline
const b = iota
x = a // a is defined with the predeclared iota, so it cannot be inlined
x = b // want `Constant b should be inlined`
// Don't offer to inline in8, because the result, "x", would mean something different
// in this scope than it does in the scope where in8 is defined.
x = in8
_ = x
}
// Type aliases
//go:fix inline
type A = T // want A: `goFixInline alias`
var _ A // want `Type alias A should be inlined`
//go:fix inline
type AA = // want AA: `goFixInline alias`
A // want `Type alias A should be inlined`
var _ AA // want `Type alias AA should be inlined`
//go:fix inline
type (
B = []T // want B: `goFixInline alias`
C = map[*string][]error // want C: `goFixInline alias`
)
var _ B // want `Type alias B should be inlined`
var _ C // want `Type alias C should be inlined`
//go:fix inline
type E = map[[Uno]string][]*T // want `invalid //go:fix inline directive: array types not supported`
var _ E // nothing should happen here
// literal array lengths are OK
//
//go:fix inline
type EL = map[[2]string][]*T // want EL: `goFixInline alias`
var _ EL // want `Type alias EL should be inlined`
//go:fix inline
type F = map[internal.T]T // want F: `goFixInline alias`
var _ F // want `Type alias F should be inlined`
//go:fix inline
type G = []chan *internal.T // want G: `goFixInline alias`
var _ G // want `Type alias G should be inlined`
// local shadowing
func _() {
type string = int
const T = 1
var _ B // nope: B's RHS contains T, which is shadowed
var _ C // nope: C's RHS contains string, which is shadowed
}
// local inlining
func _[P any]() {
const a = 1
//go:fix inline
const b = a
x := b // want `Constant b should be inlined`
//go:fix inline
type u = []P
var y u // want `Type alias u should be inlined`
_ = x
_ = y
}
// generic type aliases
//go:fix inline
type (
Mapset[T comparable] = map[T]bool // want Mapset: `goFixInline alias`
Pair[X, Y any] = struct { // want Pair: `goFixInline alias`
X X
Y Y
}
)
var _ Mapset[int] // want `Type alias Mapset\[int\] should be inlined`
var _ Pair[T, string] // want `Type alias Pair\[T, string\] should be inlined`
func _[V any]() {
//go:fix inline
type M[K comparable] = map[K]V
var _ M[int] // want `Type alias M\[int\] should be inlined`
}
================================================
FILE: go/analysis/passes/inline/testdata/src/a/a.go.golden
================================================
package a
import "a/internal"
// Functions.
func f() {
_ = one // want `Call of a.One should be inlined`
_ = 2 // want `Call of \(a.T\).Two should be inlined`
}
type T struct{}
//go:fix inline
func One() int { return one } // want One:`goFixInline a.One`
const one = 1
//go:fix inline
func (T) Two() int { return 2 } // want Two:`goFixInline \(a.T\).Two`
// Constants.
const Uno = 1
//go:fix inline
const In1 = Uno // want In1: `goFixInline const "a".Uno`
const (
no1 = one
//go:fix inline
In2 = one // want In2: `goFixInline const "a".one`
)
//go:fix inline
const (
in3 = one
in4 = one
bad1 = 1 // want `invalid //go:fix inline directive: const value is not the name of another constant`
)
//go:fix inline
const in5,
in6,
bad2 = one, one,
one + 1 // want `invalid //go:fix inline directive: const value is not the name of another constant`
// Make sure we don't crash on iota consts, but still process the whole decl.
//
//go:fix inline
const (
a = iota // want `invalid //go:fix inline directive: const value is iota`
b
in7 = one
)
func _() {
x := Uno // want `Constant In1 should be inlined`
x = one // want `Constant In2 should be inlined`
x = one // want `Constant in3 should be inlined`
x = one // want `Constant in4 should be inlined`
x = one // want `Constant in5 should be inlined`
x = one // want `Constant in6 should be inlined`
x = one // want `Constant in7 should be inlined`
x = no1
_ = x
in1 := 1 // don't inline lvalues
_ = in1
}
const (
x = 1
//go:fix inline
in8 = x
)
//go:fix inline
const D = internal.D // want D: `goFixInline const "a/internal".D`
func shadow() {
var x int // shadows x at package scope
//go:fix inline
const a = iota // want `invalid //go:fix inline directive: const value is iota`
const iota = 2
// Below this point, iota is an ordinary constant.
//go:fix inline
const b = iota
x = a // a is defined with the predeclared iota, so it cannot be inlined
x = iota // want `Constant b should be inlined`
// Don't offer to inline in8, because the result, "x", would mean something different
// in this scope than it does in the scope where in8 is defined.
x = in8
_ = x
}
// Type aliases
//go:fix inline
type A = T // want A: `goFixInline alias`
var _ T // want `Type alias A should be inlined`
//go:fix inline
type AA = // want AA: `goFixInline alias`
T // want `Type alias A should be inlined`
var _ A // want `Type alias AA should be inlined`
//go:fix inline
type (
B = []T // want B: `goFixInline alias`
C = map[*string][]error // want C: `goFixInline alias`
)
var _ []T // want `Type alias B should be inlined`
var _ map[*string][]error // want `Type alias C should be inlined`
//go:fix inline
type E = map[[Uno]string][]*T // want `invalid //go:fix inline directive: array types not supported`
var _ E // nothing should happen here
// literal array lengths are OK
//
//go:fix inline
type EL = map[[2]string][]*T // want EL: `goFixInline alias`
var _ map[[2]string][]*T // want `Type alias EL should be inlined`
//go:fix inline
type F = map[internal.T]T // want F: `goFixInline alias`
var _ map[internal.T]T // want `Type alias F should be inlined`
//go:fix inline
type G = []chan *internal.T // want G: `goFixInline alias`
var _ []chan *internal.T // want `Type alias G should be inlined`
// local shadowing
func _() {
type string = int
const T = 1
var _ B // nope: B's RHS contains T, which is shadowed
var _ C // nope: C's RHS contains string, which is shadowed
}
// local inlining
func _[P any]() {
const a = 1
//go:fix inline
const b = a
x := a // want `Constant b should be inlined`
//go:fix inline
type u = []P
var y []P // want `Type alias u should be inlined`
_ = x
_ = y
}
// generic type aliases
//go:fix inline
type (
Mapset[T comparable] = map[T]bool // want Mapset: `goFixInline alias`
Pair[X, Y any] = struct { // want Pair: `goFixInline alias`
X X
Y Y
}
)
var _ map[int]bool // want `Type alias Mapset\[int\] should be inlined`
var _ struct {
X T
Y string
} // want `Type alias Pair\[T, string\] should be inlined`
func _[V any]() {
//go:fix inline
type M[K comparable] = map[K]V
var _ map[int]V // want `Type alias M\[int\] should be inlined`
}
================================================
FILE: go/analysis/passes/inline/testdata/src/a/internal/d.go
================================================
// According to the go toolchain's rule about internal packages,
// this package is visible to package a, but not package b.
package internal
const D = 1
type T int
================================================
FILE: go/analysis/passes/inline/testdata/src/b/b.go
================================================
package b
import "a"
import . "c"
func f() {
a.One() // want `cannot inline call to a.One because body refers to non-exported one`
new(a.T).Two() // want `Call of \(a.T\).Two should be inlined`
}
//go:fix inline
const in2 = a.Uno
//go:fix inline
const in3 = C // c.C, by dot import
func g() {
x := a.In1 // want `Constant a\.In1 should be inlined`
a := 1
// Although the package identifier "a" is shadowed here,
// a second import of "a" will be added with a new package identifer.
x = in2 // want `Constant in2 should be inlined`
x = in3 // want `Constant in3 should be inlined`
_ = a
_ = x
}
const d = a.D // nope: a.D refers to a constant in a package that is not visible here.
var _ a.A // want `Type alias a\.A should be inlined`
var _ a.B // want `Type alias a\.B should be inlined`
var _ a.C // want `Type alias a\.C should be inlined`
var _ R // want `Type alias R should be inlined`
var _ a.G // nope: a.G refers to a type in a package that is not visible here
================================================
FILE: go/analysis/passes/inline/testdata/src/b/b.go.golden
================================================
package b
import a0 "a"
import "io"
import "a"
import . "c"
func f() {
a.One() // want `cannot inline call to a.One because body refers to non-exported one`
_ = 2 // want `Call of \(a.T\).Two should be inlined`
}
//go:fix inline
const in2 = a.Uno
//go:fix inline
const in3 = C // c.C, by dot import
func g() {
x := a.Uno // want `Constant a\.In1 should be inlined`
a := 1
// Although the package identifier "a" is shadowed here,
// a second import of "a" will be added with a new package identifer.
x = a0.Uno // want `Constant in2 should be inlined`
x = C // want `Constant in3 should be inlined`
_ = a
_ = x
}
const d = a.D // nope: a.D refers to a constant in a package that is not visible here.
var _ a.T // want `Type alias a\.A should be inlined`
var _ []a.T // want `Type alias a\.B should be inlined`
var _ map[*string][]error // want `Type alias a\.C should be inlined`
var _ map[io.Reader]io.Reader // want `Type alias R should be inlined`
var _ a.G // nope: a.G refers to a type in a package that is not visible here
================================================
FILE: go/analysis/passes/inline/testdata/src/binding_false/a.go
================================================
package a
//go:fix inline
func f(x, y int) int { // want f:`goFixInline a.f`
return y + x
}
func g() {
f(1, 2) // want `Call of a.f should be inlined`
f(h(1), h(2))
}
func h(int) int
================================================
FILE: go/analysis/passes/inline/testdata/src/binding_false/a.go.golden
================================================
package a
//go:fix inline
func f(x, y int) int { // want f:`goFixInline a.f`
return y + x
}
func g() {
_ = 2 + 1 // want `Call of a.f should be inlined`
f(h(1), h(2))
}
func h(int) int
================================================
FILE: go/analysis/passes/inline/testdata/src/binding_true/a.go
================================================
package a
//go:fix inline
func f(x, y int) int { // want f:`goFixInline a.f`
return y + x
}
func g() {
f(1, 2) // want `Call of a.f should be inlined`
f(h(1), h(2)) // want `Call of a.f should be inlined`
}
func h(int) int
================================================
FILE: go/analysis/passes/inline/testdata/src/binding_true/a.go.golden
================================================
package a
//go:fix inline
func f(x, y int) int { // want f:`goFixInline a.f`
return y + x
}
func g() {
_ = 2 + 1 // want `Call of a.f should be inlined`
var x int = h(1)
_ = h(2) + x // want `Call of a.f should be inlined`
}
func h(int) int
================================================
FILE: go/analysis/passes/inline/testdata/src/c/c.go
================================================
package c
// This package is dot-imported by package b.
import "io"
const C = 1
//go:fix inline
type R = map[io.Reader]io.Reader
================================================
FILE: go/analysis/passes/inline/testdata/src/directive/directive.go
================================================
package directive
// Functions.
func f() {
One()
new(T).Two()
}
type T struct{}
//go:fix inline
func One() int { return one } // want One:`goFixInline directive.One`
const one = 1
//go:fix inline
func (T) Two() int { return 2 } // want Two:`goFixInline \(directive.T\).Two`
// Constants.
const Uno = 1
//go:fix inline
const In1 = Uno // want In1: `goFixInline const "directive".Uno`
const (
no1 = one
//go:fix inline
In2 = one // want In2: `goFixInline const "directive".one`
)
//go:fix inline
const bad1 = 1 // want `invalid //go:fix inline directive: const value is not the name of another constant`
//go:fix inline
const in5,
in6,
bad2 = one, one,
one + 1 // want `invalid //go:fix inline directive: const value is not the name of another constant`
// Make sure we don't crash on iota consts, but still process the whole decl.
//
//go:fix inline
const (
a = iota // want `invalid //go:fix inline directive: const value is iota`
b
in7 = one
)
const (
x = 1
//go:fix inline
in8 = x
)
//go:fix inline
const in9 = iota // want `invalid //go:fix inline directive: const value is iota`
//go:fix inline
type E = map[[Uno]string][]*T // want `invalid //go:fix inline directive: array types not supported`
================================================
FILE: go/analysis/passes/inline/testdata/src/directive/directive.go.golden
================================================
package golden
import "a/internal"
// Functions.
func f() {
One()
new(T).Two()
}
type T struct{}
//go:fix inline
func One() int { return one }
const one = 1
//go:fix inline
func (T) Two() int { return 2 }
// Constants.
const Uno = 1
//go:fix inline
const In1 = Uno // want In1: `goFixInline const "a".Uno`
const (
no1 = one
//go:fix inline
In2 = one // want In2: `goFixInline const "a".one`
)
//go:fix inline
const bad1 = 1 // want `invalid //go:fix inline directive: const value is not the name of another constant`
//go:fix inline
const in5,
in6,
bad2 = one, one,
one + 1 // want `invalid //go:fix inline directive: const value is not the name of another constant`
// Make sure we don't crash on iota consts, but still process the whole decl.
//
//go:fix inline
const (
a = iota // want `invalid //go:fix inline directive: const value is iota`
b
in7 = one
)
const (
x = 1
//go:fix inline
in8 = x
)
//go:fix inline
const a = iota // want `invalid //go:fix inline directive: const value is iota`
//go:fix inline
type E = map[[Uno]string][]*T // want `invalid //go:fix inline directive: array types not supported`
// literal array lengths are OK
//
//go:fix inline
type EL = map[[2]string][]*T // want EL: `goFixInline alias`
================================================
FILE: go/analysis/passes/inline/testdata/src/issue76190.txtar
================================================
This test checks that calls are not inlined when the call appears in a specific
test of the function. (Even deprecated functions deserve tests.)
Variants:
- functions (TestF) vs methods (TestT_F)
- optional comment suffixes (TestSYMBOL_comment)
- in-package test vs external test
- test of same or different package from symbol
-- go.mod --
module example.com
-- a/a.go --
package a
//go:fix inline
func F() { print("F") } // want F:"goFixInline a.F"
//go:fix inline
func G() { print("G") } // want G:"goFixInline a.G"
type T int
//go:fix inline
func (T) F() { print("T.F") } // want F:`goFixInline \(a.T\).F`
//go:fix inline
func (T) G() { print("T.G") } // want G:`goFixInline \(a.T\).G`
-- a/a_test.go --
package a
import "testing"
func TestF(t *testing.T) {
F() // not inlined
G() // want "Call of a.G should be inlined"
}
func TestG_comment(t *testing.T) {
F() // want "Call of a.F should be inlined"
G() // not inlined
}
func TestT_F(t *testing.T) {
T(0).F() // not inlined
T(0).G() // want `Call of \(a.T\).G should be inlined`
}
func TestT_G(t *testing.T) {
T(0).F() // want `Call of \(a.T\).F should be inlined`
T(0).G() // not inlined
}
-- a/a_test.go.golden --
package a
import "testing"
func TestF(t *testing.T) {
F() // not inlined
print("G") // want "Call of a.G should be inlined"
}
func TestG_comment(t *testing.T) {
print("F") // want "Call of a.F should be inlined"
G() // not inlined
}
func TestT_F(t *testing.T) {
T(0).F() // not inlined
print("T.G") // want `Call of \(a.T\).G should be inlined`
}
func TestT_G(t *testing.T) {
print("T.F") // want `Call of \(a.T\).F should be inlined`
T(0).G() // not inlined
}
-- a/a_x_test.go --
package a_test
import (
"example.com/a"
"testing"
)
func TestF(t *testing.T) {
a.F() // not inlined
a.G() // want "Call of a.G should be inlined"
}
func TestG_comment(t *testing.T) {
a.F() // want "Call of a.F should be inlined"
a.G() // not inlined
}
func TestT_F(t *testing.T) {
a.T(0).F() // not inlined
a.T(0).G() // want `Call of \(a.T\).G should be inlined`
}
func TestT_G(t *testing.T) {
a.T(0).F() // want `Call of \(a.T\).F should be inlined`
a.T(0).G() // not inlined
}
-- a/a_x_test.go.golden --
package a_test
import (
"example.com/a"
"testing"
)
func TestF(t *testing.T) {
a.F() // not inlined
print("G") // want "Call of a.G should be inlined"
}
func TestG_comment(t *testing.T) {
print("F") // want "Call of a.F should be inlined"
a.G() // not inlined
}
func TestT_F(t *testing.T) {
a.T(0).F() // not inlined
print("T.G") // want `Call of \(a.T\).G should be inlined`
}
func TestT_G(t *testing.T) {
print("T.F") // want `Call of \(a.T\).F should be inlined`
a.T(0).G() // not inlined
}
-- b/b_test.go --
package b_test
import (
"example.com/a"
"testing"
)
func TestF(t *testing.T) {
a.F() // want "Call of a.F should be inlined"
}
-- b/b_test.go.golden --
package b_test
import (
"testing"
)
func TestF(t *testing.T) {
print("F") // want "Call of a.F should be inlined"
}
================================================
FILE: go/analysis/passes/inline/testdata/src/issue77610.txtar
================================================
This test verifies that unused imports conservatively added by the inliner are
removed in FormatSourceRemoveImports.
-- go.mod --
module example.com
-- a/a.go --
package a
import "io"
//go:fix inline
func OldHello(w io.Writer) error { // want OldHello:"goFixInline a.OldHello"
return NewHello(w)
}
func NewHello(w io.Writer) error {
_, err := w.Write([]byte("Hello, World!"))
return err
}
-- b/b.go --
package b
import (
"strings"
"example.com/a"
)
func _() {
var buf strings.Builder
// The inliner wants to add an import for "io"; make sure post-processing of the fix removes it.
a.OldHello(&buf) // want "Call of a.OldHello should be inlined"
}
-- b/b.go.golden --
package b
import (
"strings"
"example.com/a"
)
func _() {
var buf strings.Builder
// The inliner wants to add an import for "io"; make sure post-processing of the fix removes it.
a.NewHello(&buf) // want "Call of a.OldHello should be inlined"
}
-- c/c.go --
package c
import "encoding/json"
//go:fix inline
func OldProcess(m json.Marshaler) error { // want OldProcess:"goFixInline c.OldProcess"
return NewProcess(m)
}
func NewProcess(m json.Marshaler) error {
_, err := m.MarshalJSON()
return err
}
-- d/d.go --
package d
import (
"time"
"example.com/c"
)
func _() {
t := time.Now()
// The inliner wants to add an import for "encoding/json"; make sure post-processing of the fix removes it.
c.OldProcess(t) // want "Call of c.OldProcess should be inlined"
}
-- d/d.go.golden --
package d
import (
"time"
"example.com/c"
)
func _() {
t := time.Now()
// The inliner wants to add an import for "encoding/json"; make sure post-processing of the fix removes it.
c.NewProcess(t) // want "Call of c.OldProcess should be inlined"
}
================================================
FILE: go/analysis/passes/inline/testdata/src/issue77844.txtar
================================================
-- go.mod --
module example.com
go 1.24
-- lib/lib.go --
package lib
//go:fix inline
type Alias[T any] = []T
-- other/other.go --
package other
type Other int
-- main/main.go --
package main
import (
"example.com/lib"
"example.com/other"
)
func _() {
var _ lib.Alias[other.Other] // want "Type alias lib.Alias.other.Other. should be inlined"
}
================================================
FILE: go/analysis/passes/inline/testdata/src/rmimport/rmimport.go
================================================
package rmimport
import "a"
// Test that application of two fixes that each remove the second-last
// import of "a" results in removal of the import. This is implemented
// by the general analysis fix logic, not by any one analyzer.
func _() {
print(a.T{}.Two()) // want `should be inlined`
print(a.T{}.Two()) // want `should be inlined`
}
================================================
FILE: go/analysis/passes/inline/testdata/src/rmimport/rmimport.go.golden
================================================
package rmimport
// Test that application of two fixes that each remove the second-last
// import of "a" results in removal of the import. This is implemented
// by the general analysis fix logic, not by any one analyzer.
func _() {
print(2) // want `should be inlined`
print(2) // want `should be inlined`
}
================================================
FILE: go/analysis/passes/inspect/inspect.go
================================================
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package inspect defines an Analyzer that provides an AST inspector
// (golang.org/x/tools/go/ast/inspector.Inspector) for the syntax trees
// of a package. It is only a building block for other analyzers.
//
// Example of use in another analysis:
//
// import (
// "golang.org/x/tools/go/analysis"
// "golang.org/x/tools/go/analysis/passes/inspect"
// "golang.org/x/tools/go/ast/inspector"
// )
//
// var Analyzer = &analysis.Analyzer{
// ...
// Requires: []*analysis.Analyzer{inspect.Analyzer},
// }
//
// func run(pass *analysis.Pass) (interface{}, error) {
// inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
// inspect.Preorder(nil, func(n ast.Node) {
// ...
// })
// return nil, nil
// }
package inspect
import (
"reflect"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/ast/inspector"
)
var Analyzer = &analysis.Analyzer{
Name: "inspect",
Doc: "optimize AST traversal for later passes",
URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/inspect",
Run: run,
RunDespiteErrors: true,
ResultType: reflect.TypeFor[*inspector.Inspector](),
}
func run(pass *analysis.Pass) (any, error) {
return inspector.New(pass.Files), nil
}
================================================
FILE: go/analysis/passes/internal/gofixdirective/gofixdirective.go
================================================
// Copyright 2025 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package gofixdirective searches for and validates go:fix directives. The
// go/analysis/passes/inline package uses findgofix to perform inlining.
// The go/analysis/passes/gofix package uses findgofix to check for problems
// with go:fix directives.
//
// gofixdirective is separate from gofix to avoid depending on refactor/inline,
// which is large.
package gofixdirective
// This package is tested by go/analysis/passes/inline.
import (
"go/ast"
"go/token"
"go/types"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/ast/inspector"
internalastutil "golang.org/x/tools/internal/astutil"
)
// A Handler handles language entities with go:fix directives.
type Handler interface {
HandleFunc(*ast.FuncDecl)
HandleAlias(*ast.TypeSpec)
HandleConst(name, rhs *ast.Ident)
}
// Find finds functions and constants annotated with an appropriate "//go:fix"
// comment (the syntax proposed by #32816), and calls handler methods for each one.
// h may be nil.
func Find(pass *analysis.Pass, root inspector.Cursor, h Handler) {
for cur := range root.Preorder((*ast.FuncDecl)(nil), (*ast.GenDecl)(nil)) {
switch decl := cur.Node().(type) {
case *ast.FuncDecl:
findFunc(decl, h)
case *ast.GenDecl:
if decl.Tok != token.CONST && decl.Tok != token.TYPE {
continue
}
declInline := hasFixInline(decl.Doc)
// Accept inline directives on the entire decl as well as individual specs.
for _, spec := range decl.Specs {
switch spec := spec.(type) {
case *ast.TypeSpec: // Tok == TYPE
findAlias(pass, spec, declInline, h)
case *ast.ValueSpec: // Tok == CONST
findConst(pass, spec, declInline, h)
}
}
}
}
}
func findFunc(decl *ast.FuncDecl, h Handler) {
if !hasFixInline(decl.Doc) {
return
}
if h != nil {
h.HandleFunc(decl)
}
}
func findAlias(pass *analysis.Pass, spec *ast.TypeSpec, declInline bool, h Handler) {
if !declInline && !hasFixInline(spec.Doc) {
return
}
if !spec.Assign.IsValid() {
pass.Reportf(spec.Pos(), "invalid //go:fix inline directive: not a type alias")
return
}
// Disallow inlines of type expressions containing array types.
// Given an array type like [N]int where N is a named constant, go/types provides
// only the value of the constant as an int64. So inlining A in this code:
//
// const N = 5
// type A = [N]int
//
// would result in [5]int, breaking the connection with N.
for n := range ast.Preorder(spec.Type) {
if ar, ok := n.(*ast.ArrayType); ok && ar.Len != nil {
// Make an exception when the array length is a literal int.
if lit, ok := ast.Unparen(ar.Len).(*ast.BasicLit); ok && lit.Kind == token.INT {
continue
}
pass.Reportf(spec.Pos(), "invalid //go:fix inline directive: array types not supported")
return
}
}
if h != nil {
h.HandleAlias(spec)
}
}
func findConst(pass *analysis.Pass, spec *ast.ValueSpec, declInline bool, h Handler) {
specInline := hasFixInline(spec.Doc)
if declInline || specInline {
for i, nameIdent := range spec.Names {
if i >= len(spec.Values) {
// Possible following an iota.
break
}
var rhsIdent *ast.Ident
switch val := spec.Values[i].(type) {
case *ast.Ident:
// Constants defined with the predeclared iota cannot be inlined.
if pass.TypesInfo.Uses[val] == builtinIota {
pass.Reportf(val.Pos(), "invalid //go:fix inline directive: const value is iota")
return
}
rhsIdent = val
case *ast.SelectorExpr:
rhsIdent = val.Sel
default:
pass.Reportf(val.Pos(), "invalid //go:fix inline directive: const value is not the name of another constant")
return
}
if h != nil {
h.HandleConst(nameIdent, rhsIdent)
}
}
}
}
// hasFixInline reports the presence of a "//go:fix inline" directive
// in the comments.
func hasFixInline(cg *ast.CommentGroup) bool {
for _, d := range internalastutil.Directives(cg) {
if d.Tool == "go" && d.Name == "fix" && d.Args == "inline" {
return true
}
}
return false
}
var builtinIota = types.Universe.Lookup("iota")
================================================
FILE: go/analysis/passes/loopclosure/doc.go
================================================
// Copyright 2023 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package loopclosure defines an Analyzer that checks for references to
// enclosing loop variables from within nested functions.
//
// # Analyzer loopclosure
//
// loopclosure: check references to loop variables from within nested functions
//
// This analyzer reports places where a function literal references the
// iteration variable of an enclosing loop, and the loop calls the function
// in such a way (e.g. with go or defer) that it may outlive the loop
// iteration and possibly observe the wrong value of the variable.
//
// Note: An iteration variable can only outlive a loop iteration in Go versions <=1.21.
// In Go 1.22 and later, the loop variable lifetimes changed to create a new
// iteration variable per loop iteration. (See go.dev/issue/60078.)
//
// In this example, all the deferred functions run after the loop has
// completed, so all observe the final value of v [ 0 {
g.Go(func() error {
print(i) // want "loop variable i captured by func literal"
return nil
})
} else {
g.Go(func() error {
print(v) // want "loop variable v captured by func literal"
return nil
})
}
}
// Do not match other Group.Go cases
g1 := new(Group)
for i, v := range s {
g1.Go(func() error {
print(i)
print(v)
return nil
})
}
}
// Real-world example from #16520, slightly simplified
func _() {
var nodes []interface{}
critical := new(errgroup.Group)
others := sync.WaitGroup{}
isCritical := func(node interface{}) bool { return false }
run := func(node interface{}) error { return nil }
for _, node := range nodes {
if isCritical(node) {
critical.Go(func() error {
return run(node) // want "loop variable node captured by func literal"
})
} else {
others.Add(1)
go func() {
_ = run(node) // want "loop variable node captured by func literal"
others.Done()
}()
}
}
}
================================================
FILE: go/analysis/passes/loopclosure/testdata/src/a/b.go
================================================
// Copyright 2022 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package testdata
// B is declared in a separate file to test that object resolution spans the
// entire package.
var B int
================================================
FILE: go/analysis/passes/loopclosure/testdata/src/golang.org/x/sync/errgroup/errgroup.go
================================================
// Package errgroup synthesizes Go's package "golang.org/x/sync/errgroup",
// which is used in unit-testing.
package errgroup
type Group struct {
}
func (g *Group) Go(f func() error) {
go func() {
f()
}()
}
================================================
FILE: go/analysis/passes/loopclosure/testdata/src/subtests/subtest.go
================================================
// Copyright 2022 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// This file contains legacy tests that the loopclosure analyzer detects leaked
// references via parallel subtests.
// Legacy expectations are incorrect after go1.22.
package subtests
import (
"testing"
)
// T is used to test that loopclosure only matches T.Run when T is from the
// testing package.
type T struct{}
// Run should not match testing.T.Run. Note that the second argument is
// intentionally a *testing.T, not a *T, so that we can check both
// testing.T.Parallel inside a T.Run, and a T.Parallel inside a testing.T.Run.
func (t *T) Run(string, func(*testing.T)) {
}
func (t *T) Parallel() {}
func _(t *testing.T) {
for i, test := range []int{1, 2, 3} {
// Check that parallel subtests are identified.
t.Run("", func(t *testing.T) {
t.Parallel()
println(i) // want "loop variable i captured by func literal"
println(test) // want "loop variable test captured by func literal"
})
// Check that serial tests are OK.
t.Run("", func(t *testing.T) {
println(i)
println(test)
})
// Check that the location of t.Parallel matters.
t.Run("", func(t *testing.T) {
println(i)
println(test)
t.Parallel()
println(i) // want "loop variable i captured by func literal"
println(test) // want "loop variable test captured by func literal"
})
// Check that *testing.T value matters.
t.Run("", func(t *testing.T) {
var x testing.T
x.Parallel()
println(i)
println(test)
})
// Check that shadowing the loop variables within the test literal is OK if
// it occurs before t.Parallel().
t.Run("", func(t *testing.T) {
i := i
test := test
t.Parallel()
println(i)
println(test)
})
// Check that shadowing the loop variables within the test literal is Not
// OK if it occurs after t.Parallel().
t.Run("", func(t *testing.T) {
t.Parallel()
i := i // want "loop variable i captured by func literal"
test := test // want "loop variable test captured by func literal"
println(i) // OK
println(test) // OK
})
// Check uses in nested blocks.
t.Run("", func(t *testing.T) {
t.Parallel()
{
println(i) // want "loop variable i captured by func literal"
println(test) // want "loop variable test captured by func literal"
}
})
// Check that we catch uses in nested subtests.
t.Run("", func(t *testing.T) {
t.Parallel()
t.Run("", func(t *testing.T) {
println(i) // want "loop variable i captured by func literal"
println(test) // want "loop variable test captured by func literal"
})
})
// Check that there is no diagnostic if t is not a *testing.T.
t.Run("", func(_ *testing.T) {
t := &T{}
t.Parallel()
println(i)
println(test)
})
// Check that there is no diagnostic when a jump to a label may have caused
// the call to t.Parallel to have been skipped.
t.Run("", func(t *testing.T) {
if true {
goto Test
}
t.Parallel()
Test:
println(i)
println(test)
})
// Check that there is no diagnostic when a jump to a label may have caused
// the loop variable reference to be skipped, but there is a diagnostic
// when both the call to t.Parallel and the loop variable reference occur
// after the final label in the block.
t.Run("", func(t *testing.T) {
if true {
goto Test
}
t.Parallel()
println(i) // maybe OK
Test:
t.Parallel()
println(test) // want "loop variable test captured by func literal"
})
// Check that multiple labels are handled.
t.Run("", func(t *testing.T) {
if true {
goto Test1
} else {
goto Test2
}
Test1:
Test2:
t.Parallel()
println(test) // want "loop variable test captured by func literal"
})
// Check that we do not have problems when t.Run has a single argument.
fn := func() (string, func(t *testing.T)) { return "", nil }
t.Run(fn())
}
}
// Check that there is no diagnostic when loop variables are shadowed within
// the loop body.
func _(t *testing.T) {
for i, test := range []int{1, 2, 3} {
i := i
test := test
t.Run("", func(t *testing.T) {
t.Parallel()
println(i)
println(test)
})
}
}
// Check that t.Run must be *testing.T.Run.
func _(t *T) {
for i, test := range []int{1, 2, 3} {
t.Run("", func(t *testing.T) {
t.Parallel()
println(i)
println(test)
})
}
}
// Check that the top-level must be parallel in order to cause a diagnostic.
//
// From https://pkg.go.dev/testing:
//
// "Run does not return until parallel subtests have completed, providing a
// way to clean up after a group of parallel tests"
func _(t *testing.T) {
for _, test := range []int{1, 2, 3} {
// In this subtest, a/b must complete before the synchronous subtest "a"
// completes, so the reference to test does not escape the current loop
// iteration.
t.Run("a", func(s *testing.T) {
s.Run("b", func(u *testing.T) {
u.Parallel()
println(test)
})
})
// In this subtest, c executes concurrently, so the reference to test may
// escape the current loop iteration.
t.Run("c", func(s *testing.T) {
s.Parallel()
s.Run("d", func(u *testing.T) {
println(test) // want "loop variable test captured by func literal"
})
})
}
}
================================================
FILE: go/analysis/passes/loopclosure/testdata/src/typeparams/typeparams.go
================================================
// Copyright 2021 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// This file contains legacy tests for the loopclosure checker for GoVersion 0 {
return false // don't stray into nested functions
}
// Look for n=SelectorExpr beneath stack=[{AssignStmt,ValueSpec} CallExpr]:
//
// ctx, cancel := context.WithCancel(...)
// ctx, cancel = context.WithCancel(...)
// var ctx, cancel = context.WithCancel(...)
//
if !isContextWithCancel(pass.TypesInfo, n) || !isCall(stack[len(stack)-1]) {
return true
}
var id *ast.Ident // id of cancel var
stmt := stack[len(stack)-2]
switch stmt := stmt.(type) {
case *ast.ValueSpec:
if len(stmt.Names) > 1 {
id = stmt.Names[1]
}
case *ast.AssignStmt:
if len(stmt.Lhs) > 1 {
id, _ = stmt.Lhs[1].(*ast.Ident)
}
}
if id != nil {
if id.Name == "_" {
pass.ReportRangef(id,
"the cancel function returned by context.%s should be called, not discarded, to avoid a context leak",
n.(*ast.SelectorExpr).Sel.Name)
} else if v, ok := pass.TypesInfo.Uses[id].(*types.Var); ok {
// If the cancel variable is defined outside function scope,
// do not analyze it.
if funcScope.Contains(v.Pos()) {
cancelvars[v] = stmt
}
} else if v, ok := pass.TypesInfo.Defs[id].(*types.Var); ok {
cancelvars[v] = stmt
}
}
return true
})
if len(cancelvars) == 0 {
return // no need to inspect CFG
}
// Obtain the CFG.
cfgs := pass.ResultOf[ctrlflow.Analyzer].(*ctrlflow.CFGs)
var g *cfg.CFG
var sig *types.Signature
switch node := node.(type) {
case *ast.FuncDecl:
sig, _ = pass.TypesInfo.Defs[node.Name].Type().(*types.Signature)
if node.Name.Name == "main" && sig.Recv() == nil && pass.Pkg.Name() == "main" {
// Returning from main.main terminates the process,
// so there's no need to cancel contexts.
return
}
g = cfgs.FuncDecl(node)
case *ast.FuncLit:
sig, _ = pass.TypesInfo.Types[node.Type].Type.(*types.Signature)
g = cfgs.FuncLit(node)
}
if sig == nil {
return // missing type information
}
// Print CFG.
if debug {
fmt.Println(g.Format(pass.Fset))
}
// Examine the CFG for each variable in turn.
// (It would be more efficient to analyze all cancelvars in a
// single pass over the AST, but seldom is there more than one.)
for v, stmt := range cancelvars {
if ret := lostCancelPath(pass, g, v, stmt, sig); ret != nil {
lineno := pass.Fset.Position(stmt.Pos()).Line
pass.ReportRangef(stmt, "the %s function is not used on all paths (possible context leak)", v.Name())
pos, end := ret.Pos(), ret.End()
// golang/go#64547: cfg.Block.Return may return a synthetic
// ReturnStmt that overflows the file.
if pass.Fset.File(pos) != pass.Fset.File(end) {
end = pos
}
pass.Report(analysis.Diagnostic{
Pos: pos,
End: end,
Message: fmt.Sprintf("this return statement may be reached without using the %s var defined on line %d", v.Name(), lineno),
})
}
}
}
func isCall(n ast.Node) bool { _, ok := n.(*ast.CallExpr); return ok }
// isContextWithCancel reports whether n is one of the qualified identifiers
// context.With{Cancel,Timeout,Deadline}.
func isContextWithCancel(info *types.Info, n ast.Node) bool {
sel, ok := n.(*ast.SelectorExpr)
if !ok {
return false
}
switch sel.Sel.Name {
case "WithCancel", "WithCancelCause",
"WithTimeout", "WithTimeoutCause",
"WithDeadline", "WithDeadlineCause":
default:
return false
}
if x, ok := sel.X.(*ast.Ident); ok {
if pkgname, ok := info.Uses[x].(*types.PkgName); ok {
return pkgname.Imported().Path() == contextPackage
}
// Import failed, so we can't check package path.
// Just check the local package name (heuristic).
return x.Name == "context"
}
return false
}
// lostCancelPath finds a path through the CFG, from stmt (which defines
// the 'cancel' variable v) to a return statement, that doesn't "use" v.
// If it finds one, it returns the return statement (which may be synthetic).
// sig is the function's type, if known.
func lostCancelPath(pass *analysis.Pass, g *cfg.CFG, v *types.Var, stmt ast.Node, sig *types.Signature) *ast.ReturnStmt {
vIsNamedResult := sig != nil && tupleContains(sig.Results(), v)
// uses reports whether stmts contain a "use" of variable v.
uses := func(pass *analysis.Pass, v *types.Var, stmts []ast.Node) bool {
found := false
for _, stmt := range stmts {
ast.Inspect(stmt, func(n ast.Node) bool {
switch n := n.(type) {
case *ast.Ident:
if pass.TypesInfo.Uses[n] == v {
found = true
}
case *ast.ReturnStmt:
// A naked return statement counts as a use
// of the named result variables.
if n.Results == nil && vIsNamedResult {
found = true
}
}
return !found
})
}
return found
}
// blockUses computes "uses" for each block, caching the result.
memo := make(map[*cfg.Block]bool)
blockUses := func(pass *analysis.Pass, v *types.Var, b *cfg.Block) bool {
res, ok := memo[b]
if !ok {
res = uses(pass, v, b.Nodes)
memo[b] = res
}
return res
}
// Find the var's defining block in the CFG,
// plus the rest of the statements of that block.
var defblock *cfg.Block
var rest []ast.Node
outer:
for _, b := range g.Blocks {
for i, n := range b.Nodes {
if n == stmt {
defblock = b
rest = b.Nodes[i+1:]
break outer
}
}
}
if defblock == nil {
panic("internal error: can't find defining block for cancel var")
}
// Is v "used" in the remainder of its defining block?
if uses(pass, v, rest) {
return nil
}
// Does the defining block return without using v?
if ret := defblock.Return(); ret != nil {
return ret
}
// Search the CFG depth-first for a path, from defblock to a
// return block, in which v is never "used".
seen := make(map[*cfg.Block]bool)
var search func(blocks []*cfg.Block) *ast.ReturnStmt
search = func(blocks []*cfg.Block) *ast.ReturnStmt {
for _, b := range blocks {
if seen[b] {
continue
}
seen[b] = true
// Prune the search if the block uses v.
if blockUses(pass, v, b) {
continue
}
// Found path to return statement?
if ret := b.Return(); ret != nil {
if debug {
fmt.Printf("found path to return in block %s\n", b)
}
return ret // found
}
// Recur
if ret := search(b.Succs); ret != nil {
if debug {
fmt.Printf(" from block %s\n", b)
}
return ret
}
}
return nil
}
return search(defblock.Succs)
}
func tupleContains(tuple *types.Tuple, v *types.Var) bool {
for v0 := range tuple.Variables() {
if v0 == v {
return true
}
}
return false
}
================================================
FILE: go/analysis/passes/lostcancel/lostcancel_test.go
================================================
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package lostcancel_test
import (
"testing"
"golang.org/x/tools/go/analysis/analysistest"
"golang.org/x/tools/go/analysis/passes/lostcancel"
)
func Test(t *testing.T) {
testdata := analysistest.TestData()
analysistest.Run(t, testdata, lostcancel.Analyzer, "a", "b", "typeparams")
}
================================================
FILE: go/analysis/passes/lostcancel/testdata/src/a/a.go
================================================
// Copyright 2016 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package a
import (
"context"
"log"
"os"
"testing"
"time"
)
var bg = context.Background()
// Check the three functions and assignment forms (var, :=, =) we look for.
// (Do these early: line numbers are fragile.)
func _() {
var _, cancel = context.WithCancel(bg) // want `the cancel function is not used on all paths \(possible context leak\)`
if false {
_ = cancel
}
} // want "this return statement may be reached without using the cancel var defined on line 20"
func _() {
_, cancel2 := context.WithDeadline(bg, time.Time{}) // want "the cancel2 function is not used..."
if false {
_ = cancel2
}
} // want "may be reached without using the cancel2 var defined on line 27"
func _() {
var cancel3 func()
_, cancel3 = context.WithTimeout(bg, 0) // want "function is not used..."
if false {
_ = cancel3
}
} // want "this return statement may be reached without using the cancel3 var defined on line 35"
func _() {
ctx, _ := context.WithCancel(bg) // want "the cancel function returned by context.WithCancel should be called, not discarded, to avoid a context leak"
ctx, _ = context.WithTimeout(bg, 0) // want "the cancel function returned by context.WithTimeout should be called, not discarded, to avoid a context leak"
ctx, _ = context.WithDeadline(bg, time.Time{}) // want "the cancel function returned by context.WithDeadline should be called, not discarded, to avoid a context leak"
_ = ctx
}
func _() {
_, cancel := context.WithCancel(bg)
defer cancel() // ok
}
func _() {
_, cancel := context.WithCancel(bg) // want "not used on all paths"
if condition {
cancel()
}
return // want "this return statement may be reached without using the cancel var"
}
func _() {
_, cancel := context.WithCancel(bg)
if condition {
cancel()
} else {
// ok: infinite loop
for {
print(0)
}
}
}
func _() {
_, cancel := context.WithCancel(bg) // want "not used on all paths"
if condition {
cancel()
} else {
for i := 0; i < 10; i++ {
print(0)
}
}
} // want "this return statement may be reached without using the cancel var"
func _() {
_, cancel := context.WithCancel(bg)
// ok: used on all paths
switch someInt {
case 0:
new(testing.T).FailNow()
case 1:
log.Fatal()
case 2:
cancel()
case 3:
print("hi")
fallthrough
default:
os.Exit(1)
}
}
func _() {
_, cancel := context.WithCancel(bg) // want "not used on all paths"
switch someInt {
case 0:
new(testing.T).FailNow()
case 1:
log.Fatal()
case 2:
cancel()
case 3:
print("hi") // falls through to implicit return
default:
os.Exit(1)
}
} // want "this return statement may be reached without using the cancel var"
func _(ch chan int) {
_, cancel := context.WithCancel(bg) // want "not used on all paths"
select {
case <-ch:
new(testing.T).FailNow()
case ch <- 2:
print("hi") // falls through to implicit return
case ch <- 1:
cancel()
default:
os.Exit(1)
}
} // want "this return statement may be reached without using the cancel var"
func _(ch chan int) {
_, cancel := context.WithCancel(bg)
// A blocking select must execute one of its cases.
select {
case <-ch:
panic(0)
}
if false {
_ = cancel
}
}
func _() {
go func() {
ctx, cancel := context.WithCancel(bg) // want "not used on all paths"
if false {
_ = cancel
}
print(ctx)
}() // want "may be reached without using the cancel var"
}
var condition bool
var someInt int
// Regression test for Go issue 16143.
func _() {
var x struct{ f func() }
x.f()
}
// Regression test for Go issue 16230.
func _() (ctx context.Context, cancel func()) {
ctx, cancel = context.WithCancel(bg)
return // a naked return counts as a load of the named result values
}
// Same as above, but for literal function.
var _ = func() (ctx context.Context, cancel func()) {
ctx, cancel = context.WithCancel(bg)
return
}
// Test for Go issue 31856.
func _() {
var cancel func()
func() {
_, cancel = context.WithCancel(bg)
}()
cancel()
}
var cancel1 func()
// Same as above, but for package-level cancel variable.
func _() {
// We assume that other uses of cancel1 exist.
_, cancel1 = context.WithCancel(bg)
}
================================================
FILE: go/analysis/passes/lostcancel/testdata/src/b/b.go
================================================
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package main
import "context"
// Return from main is handled specially.
// Since the program exits, there's no need to call cancel.
func main() {
_, cancel := context.WithCancel(nil)
if maybe {
cancel()
}
}
func notMain() {
_, cancel := context.WithCancel(nil) // want "cancel function.*not used"
if maybe {
cancel()
}
} // want "return statement.*reached without using the cancel"
var maybe bool
================================================
FILE: go/analysis/passes/lostcancel/testdata/src/typeparams/typeparams.go
================================================
// Copyright 2021 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// This file contains tests for the lostcancel checker.
package typeparams
import (
"context"
"io"
"time"
)
//
// These comment lines are ballast to ensure
// that this is L17. Add/remove as needed.
var bg = context.Background()
func _[T any]() {
var _, cancel = context.WithCancel(bg) // want `the cancel function is not used on all paths \(possible context leak\)`
if false {
_ = cancel
}
} // want "this return statement may be reached without using the cancel var defined on line 22"
func _[T any]() {
_, cancel := context.WithCancel(bg)
defer cancel() // ok
}
// User-defined Context that matches type "context.Context"
type C1[P1 any, P2 any] interface {
Deadline() (deadline time.Time, ok P1)
Done() <-chan struct{}
Err() error
Value(key P2) P2
}
func _(bg C1[bool, interface{}]) {
ctx, _ := context.WithCancel(bg) // want "the cancel function returned by context.WithCancel should be called, not discarded, to avoid a context leak"
ctx, _ = context.WithTimeout(bg, 0) // want "the cancel function returned by context.WithTimeout should be called, not discarded, to avoid a context leak"
_ = ctx
}
// User-defined Context that doesn't match type "context.Context"
type C2[P any] interface {
WithCancel(parent C1[P, bool]) (ctx C1[P, bool], cancel func())
}
func _(c C2[interface{}]) {
ctx, _ := c.WithCancel(nil) // not "context.WithCancel()"
_ = ctx
}
// Further regression test for Go issue 16143.
func _() {
type C[P any] struct{ f func() P }
var x C[int]
x.f()
}
func withCancelCause(maybe bool) {
{
_, cancel := context.WithCancelCause(bg)
defer cancel(io.EOF) // ok
}
{
_, cancel := context.WithCancelCause(bg) // want "the cancel function is not used on all paths \\(possible context leak\\)"
if maybe {
cancel(io.EOF)
}
}
} // want "this return statement may be reached without using the cancel var defined on line 70"
================================================
FILE: go/analysis/passes/modernize/any.go
================================================
// Copyright 2024 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package modernize
import (
"go/ast"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/internal/analysis/analyzerutil"
"golang.org/x/tools/internal/versions"
)
var AnyAnalyzer = &analysis.Analyzer{
Name: "any",
Doc: analyzerutil.MustExtractDoc(doc, "any"),
Requires: []*analysis.Analyzer{inspect.Analyzer},
Run: runAny,
URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/modernize#any",
}
// The any pass replaces interface{} with go1.18's 'any'.
func runAny(pass *analysis.Pass) (any, error) {
for curFile := range filesUsingGoVersion(pass, versions.Go1_18) {
for curIface := range curFile.Preorder((*ast.InterfaceType)(nil)) {
iface := curIface.Node().(*ast.InterfaceType)
if iface.Methods.NumFields() == 0 {
// Check that 'any' is not shadowed.
if lookup(pass.TypesInfo, curIface, "any") == builtinAny {
pass.Report(analysis.Diagnostic{
Pos: iface.Pos(),
End: iface.End(),
Message: "interface{} can be replaced by any",
SuggestedFixes: []analysis.SuggestedFix{{
Message: "Replace interface{} by any",
TextEdits: []analysis.TextEdit{
{
Pos: iface.Pos(),
End: iface.End(),
NewText: []byte("any"),
},
},
}},
})
}
}
}
}
return nil, nil
}
================================================
FILE: go/analysis/passes/modernize/atomictypes.go
================================================
// Copyright 2026 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package modernize
import (
"fmt"
"go/ast"
"go/token"
"go/types"
"strings"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/ast/edge"
"golang.org/x/tools/go/ast/inspector"
"golang.org/x/tools/go/types/typeutil"
"golang.org/x/tools/internal/analysis/analyzerutil"
typeindexanalyzer "golang.org/x/tools/internal/analysis/typeindex"
"golang.org/x/tools/internal/astutil"
"golang.org/x/tools/internal/goplsexport"
"golang.org/x/tools/internal/refactor"
"golang.org/x/tools/internal/typesinternal"
"golang.org/x/tools/internal/typesinternal/typeindex"
"golang.org/x/tools/internal/versions"
)
var atomicTypesAnalyzer = &analysis.Analyzer{
Name: "atomictypes",
Doc: analyzerutil.MustExtractDoc(doc, "atomictypes"),
Requires: []*analysis.Analyzer{
inspect.Analyzer,
typeindexanalyzer.Analyzer,
},
Run: runAtomic,
URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/modernize#atomictypes",
}
func init() {
// Export to gopls until this is a published modernizer.
goplsexport.AtomicTypesModernizer = atomicTypesAnalyzer
}
// TODO(mkalil): support the Pointer variants.
// Consider the following function signatures for pointer loading:
// func LoadPointer(addr *unsafe.Pointer) (val unsafe.Pointer)
// func (x *Pointer[T]) Load() *T
// Since the former uses *unsafe.Pointer while the latter uses *Pointer[T],
// we would need to determine the type T to apply the transformation, and there
// will be additional edits required to remove any *unsafe.Pointer casts.
// "LoadPointer", "StorePointer", "SwapPointer", "CompareAndSwapPointer"
// sync/atomic functions of interest. Some added in go1.19, some added in go1.23.
var syncAtomicFuncs = []string{
// Added in go1.19.
"AddInt32", "AddInt64", "AddUint32", "AddUint64", "AddUintptr",
"CompareAndSwapInt32", "CompareAndSwapInt64", "CompareAndSwapUint32", "CompareAndSwapUint64", "CompareAndSwapUintptr",
"LoadInt32", "LoadInt64", "LoadUint32", "LoadUint64", "LoadUintptr",
"StoreInt32", "StoreInt64", "StoreUint32", "StoreUint64", "StoreUintptr",
"SwapInt32", "SwapInt64", "SwapUint32", "SwapUint64", "SwapUintptr",
// Added in go1.23.
"AndInt32", "AndInt64", "AndUint32", "AndUint64", "AndUintptr",
"OrInt32", "OrInt64", "OrUint32", "OrUint64", "OrUintptr",
}
func runAtomic(pass *analysis.Pass) (any, error) {
if !typesinternal.Imports(pass.Pkg, "sync/atomic") {
return nil, nil // doesn't directly import sync/atomic
}
var (
index = pass.ResultOf[typeindexanalyzer.Analyzer].(*typeindex.Index)
info = pass.TypesInfo
)
// Gather all candidate variables v appearing
// in calls to atomic.AddInt32(&v, ...) et al.
var (
atomicPkg *types.Package
vars = make(map[*types.Var]string) // maps candidate vars v to the name of the call they appear in
)
for _, funcName := range syncAtomicFuncs {
obj := index.Object("sync/atomic", funcName)
if obj == nil {
continue
}
atomicPkg = obj.Pkg()
for curCall := range index.Calls(obj) {
call := curCall.Node().(*ast.CallExpr)
if unary, ok := call.Args[0].(*ast.UnaryExpr); ok && unary.Op == token.AND {
var v *types.Var
switch x := unary.X.(type) {
case *ast.Ident:
v, _ = info.Uses[x].(*types.Var)
case *ast.SelectorExpr:
if seln, ok := info.Selections[x]; ok {
v, _ = seln.Obj().(*types.Var)
}
}
if v != nil && !v.Exported() {
// v must be a non-exported package or local var, or a struct field.
switch v.Kind() {
case types.RecvVar, types.ParamVar, types.ResultVar:
continue // fix would change func signature
}
vars[v] = funcName
}
}
}
}
// Check that all uses of each candidate variable
// appear in calls of the form atomic.AddInt32(&v, ...).
nextvar:
for v, funcName := range vars {
var edits []analysis.TextEdit
fixFiles := make(map[*ast.File]bool) // unique files involved in the current fix
// Check the form of the declaration: var v int or struct { v int }
def, ok := index.Def(v)
if !ok {
continue
}
var (
typ ast.Expr
names []*ast.Ident
)
switch parent := def.Parent().Node().(type) {
case *ast.Field: // struct { v int }
names = parent.Names
typ = parent.Type
case *ast.ValueSpec: // var v int
if len(parent.Values) > 0 {
// e.g. var v int = 5
// skip because rewriting as `var v atomic.Int32 = 5` is invalid
continue
}
names = parent.Names
typ = parent.Type
}
if len(names) != 1 || typ == nil {
continue // v is not the sole var declared here (e.g. var x, y int32); or no explicit type
}
oldType := info.TypeOf(typ) // e.g. "int32"
newType := strings.Title(oldType.Underlying().String()) // e.g. "Int32"
// Get package prefix to avoid shadowing.
file := astutil.EnclosingFile(def)
pkgPrefix, impEdits := refactor.AddImport(pass.TypesInfo, file, "atomic", "sync/atomic", "", def.Node().Pos())
if len(impEdits) > 0 {
panic("unexpected import edits") // atomic PkgName should be in scope already
}
// Edit the type.
//
// var v int32
// ------------
// var v atomic.Int32
edits = append(edits, analysis.TextEdit{
Pos: typ.Pos(),
End: typ.End(),
NewText: fmt.Appendf(nil, "%s%s", pkgPrefix, newType),
})
fixFiles[file] = true
// Each valid use is an Ident v or Selector expr.v within an atomic.F(&...) call.
var validUses []inspector.Cursor
for cur := range index.Uses(v) {
if v.IsField() && cur.ParentEdgeKind() == edge.KeyValueExpr_Key {
continue nextvar // we cannot fix initial an value assignment T{v: 1}
}
if cur.ParentEdgeKind() == edge.SelectorExpr_Sel {
cur = cur.Parent() // ascend from v to expr.v
}
// Inv: cur is the l-value expression denoting v.
// v must appear beneath atomic.AddInt32(&v, ...) call.
valid := false
if cur.ParentEdgeKind() == edge.UnaryExpr_X &&
cur.Parent().Node().(*ast.UnaryExpr).Op == token.AND {
if ek, idx := cur.Parent().ParentEdge(); ek == edge.CallExpr_Args && idx == 0 {
curCall := cur.Parent().Parent()
call := curCall.Node().(*ast.CallExpr)
if fn, ok := typeutil.Callee(info, call).(*types.Func); ok && fn.Pkg() == atomicPkg {
valid = true
}
}
}
if !valid {
// More complex case: reject candidate.
//
// For example, cur may be an unsynchronized load (e.g. v == 0). To
// avoid a type conversion error, we'd have to rewrite this as
// v.Load(). However, this is an invalid rewrite: if the program is
// mixing atomic operations with unsynchronized reads, the author
// might have accidentally introduced a data race and the suggested
// fix could obscure the mistake. Or, if the usage is intentional,
// rewriting may result in a behavior change.
continue nextvar
}
validUses = append(validUses, cur)
}
for _, cur := range validUses {
vexpr := cur.Node()
call := cur.Parent().Parent().Node().(*ast.CallExpr)
fn := typeutil.Callee(info, call).(*types.Func)
// atomic.AddInt32(&v, ...)
// ----------------- -----
// v.Add(...)
after := vexpr.End() // LoadInt32(&v⁁)
if len(call.Args) > 1 {
after = call.Args[1].Pos() // AddInt32(&v, ⁁...)
}
verb := strings.TrimSuffix(fn.Name(), newType) // "AddInt32" => "Add"
edits = append(edits, []analysis.TextEdit{
{
Pos: call.Pos(),
End: vexpr.Pos(),
},
{
Pos: vexpr.End(),
End: after,
NewText: fmt.Appendf(nil, ".%s(", verb),
},
}...)
fixFiles[astutil.EnclosingFile(cur)] = true
}
// Check minimum Go version: go1.19, or 1.23 for the And/Or functions.
if !(analyzerutil.FileUsesGoVersion(pass, file, versions.Go1_19) ||
analyzerutil.FileUsesGoVersion(pass, file, versions.Go1_23) &&
(strings.HasPrefix(funcName, "And") || strings.HasPrefix(funcName, "Or"))) {
continue
}
// Skip if v is not local and the package has ignored files as it may be
// an incomplete transformation.
if !isLocal(v) && len(pass.IgnoredFiles) > 0 {
continue
}
pass.Report(analysis.Diagnostic{
Pos: names[0].Pos(),
End: typ.End(),
Message: fmt.Sprintf("var %s %s may be simplified using atomic.%s", v.Name(), oldType, newType),
SuggestedFixes: []analysis.SuggestedFix{{
Message: fmt.Sprintf("Replace %s by atomic.%s", oldType, newType),
TextEdits: edits,
}},
})
}
return nil, nil
}
================================================
FILE: go/analysis/passes/modernize/bloop.go
================================================
// Copyright 2024 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package modernize
import (
"fmt"
"go/ast"
"go/token"
"go/types"
"strings"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/ast/inspector"
"golang.org/x/tools/go/types/typeutil"
"golang.org/x/tools/internal/analysis/analyzerutil"
typeindexanalyzer "golang.org/x/tools/internal/analysis/typeindex"
"golang.org/x/tools/internal/astutil"
"golang.org/x/tools/internal/moreiters"
"golang.org/x/tools/internal/typesinternal"
"golang.org/x/tools/internal/typesinternal/typeindex"
"golang.org/x/tools/internal/versions"
)
var BLoopAnalyzer = &analysis.Analyzer{
Name: "bloop",
Doc: analyzerutil.MustExtractDoc(doc, "bloop"),
Requires: []*analysis.Analyzer{
inspect.Analyzer,
typeindexanalyzer.Analyzer,
},
Run: bloop,
URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/modernize#bloop",
}
// bloop updates benchmarks that use "for range b.N", replacing it
// with go1.24's b.Loop() and eliminating any preceding
// b.{Start,Stop,Reset}Timer calls.
//
// Variants:
//
// for i := 0; i < b.N; i++ {} => for b.Loop() {}
// for range b.N {}
func bloop(pass *analysis.Pass) (any, error) {
if !typesinternal.Imports(pass.Pkg, "testing") {
return nil, nil
}
var (
index = pass.ResultOf[typeindexanalyzer.Analyzer].(*typeindex.Index)
info = pass.TypesInfo
)
// edits computes the text edits for a matched for/range loop
// at the specified cursor. b is the *testing.B value, and
// (start, end) is the portion using b.N to delete.
edits := func(curLoop inspector.Cursor, b ast.Expr, start, end token.Pos) (edits []analysis.TextEdit) {
curFn, _ := enclosingFunc(curLoop)
// Within the same function, delete all calls to
// b.{Start,Stop,Timer} that precede the loop.
filter := []ast.Node{(*ast.ExprStmt)(nil), (*ast.FuncLit)(nil)}
curFn.Inspect(filter, func(cur inspector.Cursor) (descend bool) {
node := cur.Node()
if is[*ast.FuncLit](node) {
return false // don't descend into FuncLits (e.g. sub-benchmarks)
}
stmt := node.(*ast.ExprStmt)
if stmt.Pos() > start {
return false // not preceding: stop
}
if call, ok := stmt.X.(*ast.CallExpr); ok {
obj := typeutil.Callee(info, call)
if typesinternal.IsMethodNamed(obj, "testing", "B", "StopTimer", "StartTimer", "ResetTimer") {
// Delete call statement.
// TODO(adonovan): delete following newline, or
// up to start of next stmt? (May delete a comment.)
edits = append(edits, analysis.TextEdit{
Pos: stmt.Pos(),
End: stmt.End(),
})
}
}
return true
})
// Replace ...b.N... with b.Loop().
return append(edits, analysis.TextEdit{
Pos: start,
End: end,
NewText: fmt.Appendf(nil, "%s.Loop()", astutil.Format(pass.Fset, b)),
})
}
// Find all for/range statements.
loops := []ast.Node{
(*ast.ForStmt)(nil),
(*ast.RangeStmt)(nil),
}
for curFile := range filesUsingGoVersion(pass, versions.Go1_24) {
for curLoop := range curFile.Preorder(loops...) {
switch n := curLoop.Node().(type) {
case *ast.ForStmt:
// for _; i < b.N; _ {}
if cmp, ok := n.Cond.(*ast.BinaryExpr); ok && cmp.Op == token.LSS {
if sel, ok := cmp.Y.(*ast.SelectorExpr); ok &&
sel.Sel.Name == "N" &&
typesinternal.IsPointerToNamed(info.TypeOf(sel.X), "testing", "B") && usesBenchmarkNOnce(curLoop, info) {
delStart, delEnd := n.Cond.Pos(), n.Cond.End()
// Eliminate variable i if no longer needed:
// for i := 0; i < b.N; i++ {
// ...no references to i...
// }
body, _ := curLoop.LastChild()
if v := isIncrementLoop(info, n); v != nil &&
!uses(index, body, v) {
delStart, delEnd = n.Init.Pos(), n.Post.End()
}
pass.Report(analysis.Diagnostic{
// Highlight "i < b.N".
Pos: n.Cond.Pos(),
End: n.Cond.End(),
Message: "b.N can be modernized using b.Loop()",
SuggestedFixes: []analysis.SuggestedFix{{
Message: "Replace b.N with b.Loop()",
TextEdits: edits(curLoop, sel.X, delStart, delEnd),
}},
})
}
}
case *ast.RangeStmt:
// for range b.N {} -> for b.Loop() {}
//
// TODO(adonovan): handle "for i := range b.N".
if sel, ok := n.X.(*ast.SelectorExpr); ok &&
n.Key == nil &&
n.Value == nil &&
sel.Sel.Name == "N" &&
typesinternal.IsPointerToNamed(info.TypeOf(sel.X), "testing", "B") && usesBenchmarkNOnce(curLoop, info) {
pass.Report(analysis.Diagnostic{
// Highlight "range b.N".
Pos: n.Range,
End: n.X.End(),
Message: "b.N can be modernized using b.Loop()",
SuggestedFixes: []analysis.SuggestedFix{{
Message: "Replace b.N with b.Loop()",
TextEdits: edits(curLoop, sel.X, n.Range, n.X.End()),
}},
})
}
}
}
}
return nil, nil
}
// uses reports whether the subtree cur contains a use of obj.
func uses(index *typeindex.Index, cur inspector.Cursor, obj types.Object) bool {
for use := range index.Uses(obj) {
if cur.Contains(use) {
return true
}
}
return false
}
// enclosingFunc returns the cursor for the innermost Func{Decl,Lit}
// that encloses c, if any.
func enclosingFunc(c inspector.Cursor) (inspector.Cursor, bool) {
return moreiters.First(c.Enclosing((*ast.FuncDecl)(nil), (*ast.FuncLit)(nil)))
}
// usesBenchmarkNOnce reports whether a b.N loop should be modernized to b.Loop().
// Only modernize loops that are:
// 1. Directly in a benchmark function (not in nested functions)
// - b.Loop() must be called in the same goroutine as the benchmark function
// - Function literals are often used with goroutines (go func(){...})
//
// 2. The only b.N loop in that benchmark function
// - b.Loop() can only be called once per benchmark execution
// - Multiple calls result in "B.Loop called with timer stopped" error
// - Multiple loops may have complex interdependencies that are hard to analyze
func usesBenchmarkNOnce(c inspector.Cursor, info *types.Info) bool {
// Find the enclosing benchmark function
curFunc, ok := enclosingFunc(c)
if !ok {
return false
}
// Check if this is actually a benchmark function
fdecl, ok := curFunc.Node().(*ast.FuncDecl)
if !ok {
return false // not in a function; or, inside a FuncLit
}
if !isBenchmarkFunc(fdecl) {
return false
}
// Count all b.N references in this benchmark function (including nested functions)
bnRefCount := 0
filter := []ast.Node{(*ast.SelectorExpr)(nil)}
curFunc.Inspect(filter, func(cur inspector.Cursor) bool {
sel := cur.Node().(*ast.SelectorExpr)
if sel.Sel.Name == "N" &&
typesinternal.IsPointerToNamed(info.TypeOf(sel.X), "testing", "B") {
bnRefCount++
}
return true
})
// Only modernize if there's exactly one b.N reference
return bnRefCount == 1
}
// isBenchmarkFunc reports whether f is a benchmark function.
func isBenchmarkFunc(f *ast.FuncDecl) bool {
return f.Recv == nil &&
f.Name != nil &&
f.Name.IsExported() &&
strings.HasPrefix(f.Name.Name, "Benchmark") &&
f.Type.Params != nil &&
len(f.Type.Params.List) == 1
}
// isIncrementLoop reports whether loop has the form "for i := 0; ...; i++ { ... }",
// and if so, it returns the symbol for the index variable.
func isIncrementLoop(info *types.Info, loop *ast.ForStmt) *types.Var {
if assign, ok := loop.Init.(*ast.AssignStmt); ok &&
assign.Tok == token.DEFINE &&
len(assign.Rhs) == 1 &&
isZeroIntConst(info, assign.Rhs[0]) &&
is[*ast.IncDecStmt](loop.Post) &&
loop.Post.(*ast.IncDecStmt).Tok == token.INC &&
astutil.EqualSyntax(loop.Post.(*ast.IncDecStmt).X, assign.Lhs[0]) {
return info.Defs[assign.Lhs[0].(*ast.Ident)].(*types.Var)
}
return nil
}
================================================
FILE: go/analysis/passes/modernize/cmd/modernize/main.go
================================================
// Copyright 2024 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// The modernize command suggests (or, with -fix, applies) fixes that
// clarify Go code by using more modern features.
//
// See [golang.org/x/tools/go/analysis/passes/modernize] for details.
package main
import (
"golang.org/x/tools/go/analysis/multichecker"
"golang.org/x/tools/go/analysis/passes/modernize"
)
func main() { multichecker.Main(modernize.Suite...) }
================================================
FILE: go/analysis/passes/modernize/doc.go
================================================
// Copyright 2024 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
/*
Package modernize provides a suite of analyzers that suggest
simplifications to Go code, using modern language and library
features.
Each diagnostic provides a fix. Our intent is that these fixes may
be safely applied en masse without changing the behavior of your
program. In some cases the suggested fixes are imperfect and may
lead to (for example) unused imports or unused local variables,
causing build breakage. However, these problems are generally
trivial to fix. We regard any modernizer whose fix changes program
behavior to have a serious bug and will endeavor to fix it.
To apply all modernization fixes en masse, you can use the
following command:
$ go run golang.org/x/tools/go/analysis/passes/modernize/cmd/modernize@latest -fix ./...
(Do not use "go get -tool" to add gopls as a dependency of your
module; gopls commands must be built from their release branch.)
If the tool warns of conflicting fixes, you may need to run it more
than once until it has applied all fixes cleanly. This command is
not an officially supported interface and may change in the future.
Changes produced by this tool should be reviewed as usual before
being merged. In some cases, a loop may be replaced by a simple
function call, causing comments within the loop to be discarded.
Human judgment may be required to avoid losing comments of value.
The modernize suite contains many analyzers. Diagnostics from some,
such as "any" (which replaces "interface{}" with "any" where it
is safe to do so), are particularly numerous. It may ease the burden of
code review to apply fixes in two steps, the first consisting only of
fixes from the "any" analyzer, the second consisting of all
other analyzers. This can be achieved using flags, as in this example:
$ modernize -any=true -fix ./...
$ modernize -any=false -fix ./...
# Analyzer appendclipped
appendclipped: simplify append chains using slices.Concat
The appendclipped analyzer suggests replacing chains of append calls with a
single call to slices.Concat, which was added in Go 1.21. For example,
append(append(s, s1...), s2...) would be simplified to slices.Concat(s, s1, s2).
In the simple case of appending to a newly allocated slice, such as
append([]T(nil), s...), the analyzer suggests the more concise slices.Clone(s).
For byte slices, it will prefer bytes.Clone if the "bytes" package is
already imported.
This fix is only applied when the base of the append tower is a
"clipped" slice, meaning its length and capacity are equal (e.g.
x[:0:0] or []T{}). This is to avoid changing program behavior by
eliminating intended side effects on the base slice's underlying
array.
This analyzer is currently disabled by default as the
transformation does not preserve the nilness of the base slice in
all cases; see https://go.dev/issue/73557.
# Analyzer atomictypes
atomictypes: replace basic types in sync/atomic calls with atomic types
The atomictypes analyzer suggests replacing the primitive sync/atomic functions with
the strongly typed atomic wrapper types introduced in Go1.19 (e.g.
atomic.Int32). For example,
var x int32
atomic.AddInt32(&x, 1)
would become
var x atomic.Int32
x.Add(1)
The atomic types are safer because they don't allow non-atomic access, which is
a common source of bugs. These types also resolve memory alignment issues that
plagued the old atomic functions on 32-bit architectures.
# Analyzer bloop
bloop: replace for-range over b.N with b.Loop
The bloop analyzer suggests replacing benchmark loops of the form
`for i := 0; i < b.N; i++` or `for range b.N` with the more modern
`for b.Loop()`, which was added in Go 1.24.
This change makes benchmark code more readable and also removes the need for
manual timer control, so any preceding calls to b.StartTimer, b.StopTimer,
or b.ResetTimer within the same function will also be removed.
Caveats: The b.Loop() method is designed to prevent the compiler from
optimizing away the benchmark loop, which can occasionally result in
slower execution due to increased allocations in some specific cases.
Since its fix may change the performance of nanosecond-scale benchmarks,
bloop is disabled by default in the `go fix` analyzer suite; see golang/go#74967.
# Analyzer any
any: replace interface{} with any
The any analyzer suggests replacing uses of the empty interface type,
`interface{}`, with the `any` alias, which was introduced in Go 1.18.
This is a purely stylistic change that makes code more readable.
# Analyzer errorsastype
errorsastype: replace errors.As with errors.AsType[T]
This analyzer suggests fixes to simplify uses of [errors.As] of
this form:
var myerr *MyErr
if errors.As(err, &myerr) {
handle(myerr)
}
by using the less error-prone generic [errors.AsType] function,
introduced in Go 1.26:
if myerr, ok := errors.AsType[*MyErr](err); ok {
handle(myerr)
}
The fix is only offered if the var declaration has the form shown and
there are no uses of myerr outside the if statement.
# Analyzer fmtappendf
fmtappendf: replace []byte(fmt.Sprintf) with fmt.Appendf
The fmtappendf analyzer suggests replacing `[]byte(fmt.Sprintf(...))` with
`fmt.Appendf(nil, ...)`. This avoids the intermediate allocation of a string
by Sprintf, making the code more efficient. The suggestion also applies to
fmt.Sprint and fmt.Sprintln.
# Analyzer forvar
forvar: remove redundant re-declaration of loop variables
The forvar analyzer removes unnecessary shadowing of loop variables.
Before Go 1.22, it was common to write `for _, x := range s { x := x ... }`
to create a fresh variable for each iteration. Go 1.22 changed the semantics
of `for` loops, making this pattern redundant. This analyzer removes the
unnecessary `x := x` statement.
This fix only applies to `range` loops.
# Analyzer mapsloop
mapsloop: replace explicit loops over maps with calls to maps package
The mapsloop analyzer replaces loops of the form
for k, v := range x { m[k] = v }
with a single call to a function from the `maps` package, added in Go 1.23.
Depending on the context, this could be `maps.Copy`, `maps.Insert`,
`maps.Clone`, or `maps.Collect`.
The transformation to `maps.Clone` is applied conservatively, as it
preserves the nilness of the source map, which may be a subtle change in
behavior if the original code did not handle a nil map in the same way.
# Analyzer minmax
minmax: replace if/else statements with calls to min or max
The minmax analyzer simplifies conditional assignments by suggesting the use
of the built-in `min` and `max` functions, introduced in Go 1.21. For example,
if a < b { x = a } else { x = b }
is replaced by
x = min(a, b).
This analyzer avoids making suggestions for floating-point types,
as the behavior of `min` and `max` with NaN values can differ from
the original if/else statement.
# Analyzer newexpr
newexpr: simplify code by using go1.26's new(expr)
This analyzer finds declarations of functions of this form:
func varOf(x int) *int { return &x }
and suggests a fix to turn them into inlinable wrappers around
go1.26's built-in new(expr) function:
//go:fix inline
func varOf(x int) *int { return new(x) }
(The directive comment causes the 'inline' analyzer to suggest
that calls to such functions are inlined.)
In addition, this analyzer suggests a fix for each call
to one of the functions before it is transformed, so that
use(varOf(123))
is replaced by:
use(new(123))
Wrapper functions such as varOf are common when working with Go
serialization packages such as for JSON or protobuf, where pointers
are often used to express optionality.
# Analyzer omitzero
omitzero: suggest replacing omitempty with omitzero for struct fields
The omitzero analyzer identifies uses of the `omitempty` JSON struct
tag on fields that are themselves structs. For struct-typed fields,
the `omitempty` tag has no effect on the behavior of json.Marshal and
json.Unmarshal. The analyzer offers two suggestions: either remove the
tag, or replace it with `omitzero` (added in Go 1.24), which correctly
omits the field if the struct value is zero.
However, some other serialization packages (notably kubebuilder, see
https://book.kubebuilder.io/reference/markers.html) may have their own
interpretation of the `json:",omitzero"` tag, so removing it may affect
program behavior. For this reason, the omitzero modernizer will not
make changes in any package that contains +kubebuilder annotations.
Replacing `omitempty` with `omitzero` is a change in behavior. The
original code would always encode the struct field, whereas the
modified code will omit it if it is a zero-value.
# Analyzer plusbuild
plusbuild: remove obsolete //+build comments
The plusbuild analyzer suggests a fix to remove obsolete build tags
of the form:
//+build linux,amd64
in files that also contain a Go 1.18-style tag such as:
//go:build linux && amd64
(It does not check that the old and new tags are consistent;
that is the job of the 'buildtag' analyzer in the vet suite.)
# Analyzer rangeint
rangeint: replace 3-clause for loops with for-range over integers
The rangeint analyzer suggests replacing traditional for loops such
as
for i := 0; i < n; i++ { ... }
with the more idiomatic Go 1.22 style:
for i := range n { ... }
This transformation is applied only if (a) the loop variable is not
modified within the loop body and (b) the loop's limit expression
is not modified within the loop, as `for range` evaluates its
operand only once.
# Analyzer reflecttypefor
reflecttypefor: replace reflect.TypeOf(x) with TypeFor[T]()
This analyzer suggests fixes to replace uses of reflect.TypeOf(x) with
reflect.TypeFor, introduced in go1.22, when the desired runtime type
is known at compile time, for example:
reflect.TypeOf(uint32(0)) -> reflect.TypeFor[uint32]()
reflect.TypeOf((*ast.File)(nil)) -> reflect.TypeFor[*ast.File]()
It also offers a fix to simplify the constructions below, which use
reflect.TypeOf to return the runtime type for an interface type,
reflect.TypeOf((*io.Reader)(nil)).Elem()
or:
reflect.TypeOf([]io.Reader(nil)).Elem()
to:
reflect.TypeFor[io.Reader]()
No fix is offered in cases when the runtime type is dynamic, such as:
var r io.Reader = ...
reflect.TypeOf(r)
or when the operand has potential side effects.
# Analyzer slicescontains
slicescontains: replace loops with slices.Contains or slices.ContainsFunc
The slicescontains analyzer simplifies loops that check for the existence of
an element in a slice. It replaces them with calls to `slices.Contains` or
`slices.ContainsFunc`, which were added in Go 1.21.
If the expression for the target element has side effects, this
transformation will cause those effects to occur only once, not
once per tested slice element.
# Analyzer slicesdelete
slicesdelete: replace append-based slice deletion with slices.Delete
The slicesdelete analyzer suggests replacing the idiom
s = append(s[:i], s[j:]...)
with the more explicit
s = slices.Delete(s, i, j)
introduced in Go 1.21.
This analyzer is disabled by default. The `slices.Delete` function
zeros the elements between the new length and the old length of the
slice to prevent memory leaks, which is a subtle difference in
behavior compared to the append-based idiom; see https://go.dev/issue/73686.
# Analyzer slicessort
slicessort: replace sort.Slice with slices.Sort for basic types
The slicessort analyzer simplifies sorting slices of basic ordered
types. It replaces
sort.Slice(s, func(i, j int) bool { return s[i] < s[j] })
with the simpler `slices.Sort(s)`, which was added in Go 1.21.
# Analyzer stditerators
stditerators: use iterators instead of Len/At-style APIs
This analyzer suggests a fix to replace each loop of the form:
for i := 0; i < x.Len(); i++ {
use(x.At(i))
}
or its "for elem := range x.Len()" equivalent by a range loop over an
iterator offered by the same data type:
for elem := range x.All() {
use(x.At(i)
}
where x is one of various well-known types in the standard library.
# Analyzer stringscut
stringscut: replace strings.Index etc. with strings.Cut
This analyzer replaces certain patterns of use of [strings.Index] and string slicing by [strings.Cut], added in go1.18.
For example:
idx := strings.Index(s, substr)
if idx >= 0 {
return s[:idx]
}
is replaced by:
before, _, ok := strings.Cut(s, substr)
if ok {
return before
}
And:
idx := strings.Index(s, substr)
if idx >= 0 {
return
}
is replaced by:
found := strings.Contains(s, substr)
if found {
return
}
It also handles variants using [strings.IndexByte] instead of Index, or the bytes package instead of strings.
Fixes are offered only in cases in which there are no potential modifications of the idx, s, or substr expressions between their definition and use.
# Analyzer stringscutprefix
stringscutprefix: replace HasPrefix/TrimPrefix with CutPrefix
The stringscutprefix analyzer simplifies a common pattern where code first
checks for a prefix with `strings.HasPrefix` and then removes it with
`strings.TrimPrefix`. It replaces this two-step process with a single call
to `strings.CutPrefix`, introduced in Go 1.20. The analyzer also handles
the equivalent functions in the `bytes` package.
For example, this input:
if strings.HasPrefix(s, prefix) {
use(strings.TrimPrefix(s, prefix))
}
is fixed to:
if after, ok := strings.CutPrefix(s, prefix); ok {
use(after)
}
The analyzer also offers fixes to use CutSuffix in a similar way.
This input:
if strings.HasSuffix(s, suffix) {
use(strings.TrimSuffix(s, suffix))
}
is fixed to:
if before, ok := strings.CutSuffix(s, suffix); ok {
use(before)
}
# Analyzer stringsseq
stringsseq: replace ranging over Split/Fields with SplitSeq/FieldsSeq
The stringsseq analyzer improves the efficiency of iterating over substrings.
It replaces
for range strings.Split(...)
with the more efficient
for range strings.SplitSeq(...)
which was added in Go 1.24 and avoids allocating a slice for the
substrings. The analyzer also handles strings.Fields and the
equivalent functions in the bytes package.
# Analyzer stringsbuilder
stringsbuilder: replace += with strings.Builder
This analyzer replaces repeated string += string concatenation
operations with calls to Go 1.10's strings.Builder.
For example:
var s = "["
for x := range seq {
s += x
s += "."
}
s += "]"
use(s)
is replaced by:
var s strings.Builder
s.WriteString("[")
for x := range seq {
s.WriteString(x)
s.WriteString(".")
}
s.WriteString("]")
use(s.String())
This avoids quadratic memory allocation and improves performance.
The analyzer requires that all references to s before the final uses
are += operations. To avoid warning about trivial cases, at least one
must appear within a loop. The variable s must be a local
variable, not a global or parameter.
All uses of the finished string must come after the last += operation.
Each such use will be replaced by a call to strings.Builder's String method.
(These may appear within an intervening loop or function literal, since even
if s.String() is called repeatedly, it does not allocate memory.)
Often the addend is a call to fmt.Sprintf, as in this example:
var s string
for x := range seq {
s += fmt.Sprintf("%v", x)
}
which, once the suggested fix is applied, becomes:
var s strings.Builder
for x := range seq {
s.WriteString(fmt.Sprintf("%v", x))
}
The WriteString call can be further simplified to the more efficient
fmt.Fprintf(&s, "%v", x), avoiding the allocation of an intermediary.
However, stringsbuilder does not perform this simplification;
it requires staticcheck analyzer QF1012. (See https://go.dev/issue/76918.)
# Analyzer testingcontext
testingcontext: replace context.WithCancel with t.Context in tests
The testingcontext analyzer simplifies context management in tests. It
replaces the manual creation of a cancellable context,
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
with a single call to t.Context(), which was added in Go 1.24.
This change is only suggested if the `cancel` function is not used
for any other purpose.
# Analyzer unsafefuncs
unsafefuncs: replace unsafe pointer arithmetic with function calls
The unsafefuncs analyzer simplifies pointer arithmetic expressions by
replacing them with calls to helper functions such as unsafe.Add,
added in Go 1.17.
Example:
unsafe.Pointer(uintptr(ptr) + uintptr(n))
where ptr is an unsafe.Pointer, is replaced by:
unsafe.Add(ptr, n)
# Analyzer waitgroupgo
waitgroupgo: replace wg.Add(1)/go/wg.Done() with wg.Go
The waitgroupgo analyzer simplifies goroutine management with `sync.WaitGroup`.
It replaces the common pattern
wg.Add(1)
go func() {
defer wg.Done()
...
}()
with a single call to
wg.Go(func(){ ... })
which was added in Go 1.25.
*/
package modernize
================================================
FILE: go/analysis/passes/modernize/errorsastype.go
================================================
// Copyright 2025 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package modernize
import (
"go/ast"
"go/token"
"go/types"
"fmt"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/ast/edge"
"golang.org/x/tools/go/ast/inspector"
"golang.org/x/tools/internal/analysis/analyzerutil"
typeindexanalyzer "golang.org/x/tools/internal/analysis/typeindex"
"golang.org/x/tools/internal/astutil"
"golang.org/x/tools/internal/goplsexport"
"golang.org/x/tools/internal/refactor"
"golang.org/x/tools/internal/typesinternal"
"golang.org/x/tools/internal/typesinternal/typeindex"
"golang.org/x/tools/internal/versions"
)
var errorsastypeAnalyzer = &analysis.Analyzer{
Name: "errorsastype",
Doc: analyzerutil.MustExtractDoc(doc, "errorsastype"),
URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/modernize#errorsastype",
Requires: []*analysis.Analyzer{typeindexanalyzer.Analyzer},
Run: errorsastype,
}
func init() {
// Export to gopls until this is a published modernizer.
goplsexport.ErrorsAsTypeModernizer = errorsastypeAnalyzer
}
// errorsastype offers a fix to replace error.As with the newer
// errors.AsType[T] following this pattern:
//
// var myerr *MyErr
// if errors.As(err, &myerr) { ... }
//
// =>
//
// if myerr, ok := errors.AsType[*MyErr](err); ok { ... }
//
// (In principle several of these can then be chained using if/else,
// but we don't attempt that.)
//
// We offer the fix only within an if statement, but not within a
// switch case such as:
//
// var myerr *MyErr
// switch {
// case errors.As(err, &myerr):
// }
//
// because the transformation in that case would be ungainly.
//
// Note that the cmd/vet suite includes the "errorsas" analyzer, which
// detects actual mistakes in the use of errors.As. This logic does
// not belong in errorsas because the problems it fixes are merely
// stylistic.
//
// TODO(adonovan): support more cases:
//
// - Negative cases
// var myerr E
// if !errors.As(err, &myerr) { ... }
// =>
// myerr, ok := errors.AsType[E](err)
// if !ok { ... }
//
// - if myerr := new(E); errors.As(err, myerr); { ... }
//
// - if errors.As(err, myerr) && othercond { ... }
func errorsastype(pass *analysis.Pass) (any, error) {
var (
index = pass.ResultOf[typeindexanalyzer.Analyzer].(*typeindex.Index)
info = pass.TypesInfo
)
for curCall := range index.Calls(index.Object("errors", "As")) {
call := curCall.Node().(*ast.CallExpr)
if len(call.Args) < 2 {
continue // spread call: errors.As(pair())
}
v, curDeclStmt := canUseErrorsAsType(info, index, curCall)
if v == nil {
continue
}
file := astutil.EnclosingFile(curDeclStmt)
if !analyzerutil.FileUsesGoVersion(pass, file, versions.Go1_26) {
continue // errors.AsType is too new
}
// Locate identifier "As" in errors.As.
var asIdent *ast.Ident
switch n := ast.Unparen(call.Fun).(type) {
case *ast.Ident:
asIdent = n // "errors" was dot-imported
case *ast.SelectorExpr:
asIdent = n.Sel
default:
panic("no Ident for errors.As")
}
// Format the type as valid Go syntax.
// TODO(adonovan): fix: FileQualifier needs to respect
// visibility at the current point, and either fail
// or edit the imports as needed.
// TODO(adonovan): fix: TypeString is not a sound way
// to print types as Go syntax as it does not respect
// symbol visibility, etc. We need something loosely
// integrated with FileQualifier that accumulates
// import edits, and may fail (e.g. for unexported
// type or field names from other packages).
// See https://go.dev/issues/75604.
qual := typesinternal.FileQualifier(file, pass.Pkg)
errtype := types.TypeString(v.Type(), qual)
// Choose a name for the "ok" variable.
// We generate a new name only if 'ok' is already declared at
// curCall and it also used within the if-statement.
curIf := curCall.Parent()
ifScope := info.Scopes[curIf.Node().(*ast.IfStmt)]
okName := freshName(info, index, ifScope, v.Pos(), curCall, curIf, token.NoPos, "ok")
pass.Report(analysis.Diagnostic{
Pos: call.Fun.Pos(),
End: call.Fun.End(),
Message: fmt.Sprintf("errors.As can be simplified using AsType[%s]", errtype),
SuggestedFixes: []analysis.SuggestedFix{{
Message: fmt.Sprintf("Replace errors.As with AsType[%s]", errtype),
TextEdits: append(
// delete "var myerr *MyErr"
refactor.DeleteStmt(pass.Fset.File(call.Fun.Pos()), curDeclStmt),
// if errors.As (err, &myerr) { ... }
// ------------- -------------- -------- ----
// if myerr, ok := errors.AsType[*MyErr](err ); ok { ... }
analysis.TextEdit{
// insert "myerr, ok := "
Pos: call.Pos(),
End: call.Pos(),
NewText: fmt.Appendf(nil, "%s, %s := ", v.Name(), okName),
},
analysis.TextEdit{
// replace As with AsType[T]
Pos: asIdent.Pos(),
End: asIdent.End(),
NewText: fmt.Appendf(nil, "AsType[%s]", errtype),
},
analysis.TextEdit{
// delete ", &myerr"
Pos: call.Args[0].End(),
End: call.Args[1].End(),
},
analysis.TextEdit{
// insert "; ok"
Pos: call.End(),
End: call.End(),
NewText: fmt.Appendf(nil, "; %s", okName),
},
),
}},
})
}
return nil, nil
}
// canUseErrorsAsType reports whether curCall is a call to
// errors.As beneath an if statement, preceded by a
// declaration of the typed error var. The var must not be
// used outside the if statement.
func canUseErrorsAsType(info *types.Info, index *typeindex.Index, curCall inspector.Cursor) (_ *types.Var, _ inspector.Cursor) {
if curCall.ParentEdgeKind() != edge.IfStmt_Cond {
return // not beneath if statement
}
var (
curIfStmt = curCall.Parent()
ifStmt = curIfStmt.Node().(*ast.IfStmt)
)
if ifStmt.Init != nil {
return // if statement already has an init part
}
unary, ok := curCall.Node().(*ast.CallExpr).Args[1].(*ast.UnaryExpr)
if !ok || unary.Op != token.AND {
return // 2nd arg is not &var
}
id, ok := unary.X.(*ast.Ident)
if !ok {
return // not a simple ident (local var)
}
v := info.Uses[id].(*types.Var)
curDef, ok := index.Def(v)
if !ok {
return // var is not local (e.g. dot-imported)
}
// Have: if errors.As(err, &v) { ... }
// Reject if v is used outside (before or after) the
// IfStmt, since that will become its new scope.
for curUse := range index.Uses(v) {
if !curIfStmt.Contains(curUse) {
return // v used before/after if statement
}
}
if curDef.ParentEdgeKind() != edge.ValueSpec_Names {
return // v not declared by "var v T"
}
var (
curSpec = curDef.Parent() // ValueSpec
curDecl = curSpec.Parent() // GenDecl
spec = curSpec.Node().(*ast.ValueSpec)
)
if len(spec.Names) != 1 || len(spec.Values) != 0 ||
len(curDecl.Node().(*ast.GenDecl).Specs) != 1 {
return // not a simple "var v T" decl
}
// Have:
// var v *MyErr
// ...
// if errors.As(err, &v) { ... }
// with no uses of v outside the IfStmt.
return v, curDecl.Parent() // DeclStmt
}
================================================
FILE: go/analysis/passes/modernize/fmtappendf.go
================================================
// Copyright 2024 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package modernize
import (
"fmt"
"go/ast"
"go/constant"
"go/types"
"strings"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/ast/edge"
"golang.org/x/tools/internal/analysis/analyzerutil"
typeindexanalyzer "golang.org/x/tools/internal/analysis/typeindex"
"golang.org/x/tools/internal/astutil"
"golang.org/x/tools/internal/fmtstr"
"golang.org/x/tools/internal/typesinternal/typeindex"
"golang.org/x/tools/internal/versions"
)
var FmtAppendfAnalyzer = &analysis.Analyzer{
Name: "fmtappendf",
Doc: analyzerutil.MustExtractDoc(doc, "fmtappendf"),
Requires: []*analysis.Analyzer{
inspect.Analyzer,
typeindexanalyzer.Analyzer,
},
Run: fmtappendf,
URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/modernize#fmtappendf",
}
// The fmtappend function replaces []byte(fmt.Sprintf(...)) by
// fmt.Appendf(nil, ...), and similarly for Sprint, Sprintln.
func fmtappendf(pass *analysis.Pass) (any, error) {
index := pass.ResultOf[typeindexanalyzer.Analyzer].(*typeindex.Index)
for _, fn := range []types.Object{
index.Object("fmt", "Sprintf"),
index.Object("fmt", "Sprintln"),
index.Object("fmt", "Sprint"),
} {
for curCall := range index.Calls(fn) {
call := curCall.Node().(*ast.CallExpr)
if ek, idx := curCall.ParentEdge(); ek == edge.CallExpr_Args && idx == 0 {
// Is parent a T(fmt.SprintX(...)) conversion?
conv := curCall.Parent().Node().(*ast.CallExpr)
info := pass.TypesInfo
tv := info.Types[conv.Fun]
if tv.IsType() && types.Identical(tv.Type, byteSliceType) {
// Have: []byte(fmt.SprintX(...))
if len(call.Args) == 0 {
continue
}
// fmt.Sprint(f) and fmt.Append(f) have different nil semantics
// when the format produces an empty string:
// []byte(fmt.Sprintf("")) returns an empty but non-nil
// []byte{}, while fmt.Appendf(nil, "") returns nil) so we
// should skip these cases.
if fn.Name() == "Sprint" || fn.Name() == "Sprintf" {
format := info.Types[call.Args[0]].Value
if format != nil && mayFormatEmpty(constant.StringVal(format)) {
continue
}
}
// Find "Sprint" identifier.
var id *ast.Ident
switch e := ast.Unparen(call.Fun).(type) {
case *ast.SelectorExpr:
id = e.Sel // "fmt.Sprint"
case *ast.Ident:
id = e // "Sprint" after `import . "fmt"`
}
old, new := fn.Name(), strings.Replace(fn.Name(), "Sprint", "Append", 1)
edits := []analysis.TextEdit{
{
// Delete "[]byte(", including any spaces before the first argument.
Pos: conv.Pos(),
End: conv.Args[0].Pos(), // always exactly one argument in a valid byte slice conversion
},
{
// Delete ")", including any non-args (space or
// commas) that come before the right parenthesis.
// Leaving an extra comma here produces invalid
// code. (See golang/go#74709)
// Unfortunately, this and the edit above may result
// in deleting some comments.
Pos: conv.Args[0].End(),
End: conv.Rparen + 1,
},
{
Pos: id.Pos(),
End: id.End(),
NewText: []byte(new),
},
{
Pos: call.Lparen + 1,
NewText: []byte("nil, "),
},
}
if !analyzerutil.FileUsesGoVersion(pass, astutil.EnclosingFile(curCall), versions.Go1_19) {
continue
}
pass.Report(analysis.Diagnostic{
Pos: conv.Pos(),
End: conv.End(),
Message: fmt.Sprintf("Replace []byte(fmt.%s...) with fmt.%s", old, new),
SuggestedFixes: []analysis.SuggestedFix{{
Message: fmt.Sprintf("Replace []byte(fmt.%s...) with fmt.%s", old, new),
TextEdits: edits,
}},
})
}
}
}
}
return nil, nil
}
// mayFormatEmpty reports whether fmt.Sprintf might produce an empty string.
// It returns false in the following two cases:
// 1. formatStr contains non-operation characters.
// 2. formatStr contains formatting verbs besides s, v, x, X (verbs which may
// produce empty results)
//
// In all other cases it returns true.
func mayFormatEmpty(formatStr string) bool {
if formatStr == "" {
return true
}
operations, err := fmtstr.Parse(formatStr, 0)
if err != nil {
// If formatStr is malformed, the printf analyzer will report a
// diagnostic, so we can ignore this error.
// Calling Parse on a string without % formatters also returns an error,
// in which case we can safely return false.
return false
}
totalOpsLen := 0
for _, op := range operations {
totalOpsLen += len(op.Text)
if !strings.ContainsRune("svxX", rune(op.Verb.Verb)) && op.Prec.Fixed != 0 {
// A non [s, v, x, X] formatter with non-zero precision cannot
// produce an empty string.
return false
}
}
// If the format string contains non-operation characters, it cannot produce
// the empty string.
if totalOpsLen != len(formatStr) {
return false
}
// If we get here, it means that all formatting verbs are %s, %v, %x, %X,
// and there are no additional non-operation characters. We conservatively
// report that this may format as an empty string, ignoring uses of
// precision and the values of the formatter args.
return true
}
================================================
FILE: go/analysis/passes/modernize/forvar.go
================================================
// Copyright 2025 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package modernize
import (
"go/ast"
"go/token"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/internal/analysis/analyzerutil"
"golang.org/x/tools/internal/astutil"
"golang.org/x/tools/internal/refactor"
"golang.org/x/tools/internal/versions"
)
var ForVarAnalyzer = &analysis.Analyzer{
Name: "forvar",
Doc: analyzerutil.MustExtractDoc(doc, "forvar"),
Requires: []*analysis.Analyzer{inspect.Analyzer},
Run: forvar,
URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/modernize#forvar",
}
// forvar offers to fix unnecessary copying of a for variable
//
// for _, x := range foo {
// x := x // offer to remove this superfluous assignment
// }
//
// Prerequisites:
// First statement in a range loop has to be :=
// where the two idents are the same,
// and the ident is defined (:=) as a variable in the for statement.
// (Note that this 'fix' does not work for three clause loops
// because the Go spec says "The variable used by each subsequent iteration
// is declared implicitly before executing the post statement and initialized to the
// value of the previous iteration's variable at that moment.")
//
// Variant: same thing in an IfStmt.Init, when the IfStmt is the sole
// loop body statement:
//
// for _, x := range foo {
// if x := x; cond { ... }
// }
//
// (The restriction is necessary to avoid potential problems arising
// from merging two distinct variables.)
//
// This analyzer is synergistic with stditerators,
// which may create redundant "x := x" statements.
func forvar(pass *analysis.Pass) (any, error) {
for curFile := range filesUsingGoVersion(pass, versions.Go1_22) {
for curLoop := range curFile.Preorder((*ast.RangeStmt)(nil)) {
loop := curLoop.Node().(*ast.RangeStmt)
if loop.Tok != token.DEFINE {
continue
}
isLoopVarRedecl := func(stmt ast.Stmt) bool {
if assign, ok := stmt.(*ast.AssignStmt); ok &&
assign.Tok == token.DEFINE &&
len(assign.Lhs) == len(assign.Rhs) {
for i, lhs := range assign.Lhs {
if !(astutil.EqualSyntax(lhs, assign.Rhs[i]) &&
(astutil.EqualSyntax(lhs, loop.Key) ||
astutil.EqualSyntax(lhs, loop.Value))) {
return false
}
}
return true
}
return false
}
// Have: for k, v := range x { stmts }
//
// Delete the prefix of stmts that are
// of the form k := k; v := v; k, v := k, v; v, k := v, k.
for _, stmt := range loop.Body.List {
if isLoopVarRedecl(stmt) {
// { x := x; ... }
// ------
} else if ifstmt, ok := stmt.(*ast.IfStmt); ok &&
ifstmt.Init != nil &&
len(loop.Body.List) == 1 && // must be sole statement in loop body
isLoopVarRedecl(ifstmt.Init) {
// if x := x; cond {
// ------
stmt = ifstmt.Init
} else {
break // stop at first other statement
}
curStmt, _ := curLoop.FindNode(stmt)
edits := refactor.DeleteStmt(pass.Fset.File(stmt.Pos()), curStmt)
if len(edits) > 0 {
pass.Report(analysis.Diagnostic{
Pos: stmt.Pos(),
End: stmt.End(),
Message: "copying variable is unneeded",
SuggestedFixes: []analysis.SuggestedFix{{
Message: "Remove unneeded redeclaration",
TextEdits: edits,
}},
})
}
}
}
}
return nil, nil
}
================================================
FILE: go/analysis/passes/modernize/maps.go
================================================
// Copyright 2024 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package modernize
// This file defines modernizers that use the "maps" package.
import (
"fmt"
"go/ast"
"go/token"
"go/types"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/ast/inspector"
"golang.org/x/tools/internal/analysis/analyzerutil"
"golang.org/x/tools/internal/astutil"
"golang.org/x/tools/internal/refactor"
"golang.org/x/tools/internal/typeparams"
"golang.org/x/tools/internal/typesinternal"
"golang.org/x/tools/internal/versions"
)
var MapsLoopAnalyzer = &analysis.Analyzer{
Name: "mapsloop",
Doc: analyzerutil.MustExtractDoc(doc, "mapsloop"),
Requires: []*analysis.Analyzer{inspect.Analyzer},
Run: mapsloop,
URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/modernize#mapsloop",
}
// The mapsloop pass offers to simplify a loop of map insertions:
//
// for k, v := range x {
// m[k] = v
// }
//
// by a call to go1.23's maps package. There are four variants, the
// product of two axes: whether the source x is a map or an iter.Seq2,
// and whether the destination m is a newly created map:
//
// maps.Copy(m, x) (x is map)
// maps.Insert(m, x) (x is iter.Seq2)
// m = maps.Clone(x) (x is a non-nil map, m is a new map)
// m = maps.Collect(x) (x is iter.Seq2, m is a new map)
//
// A map is newly created if the preceding statement has one of these
// forms, where M is a map type:
//
// m = make(M)
// m = M{}
func mapsloop(pass *analysis.Pass) (any, error) {
// Skip the analyzer in packages where its
// fixes would create an import cycle.
if within(pass, "maps", "bytes", "runtime") {
return nil, nil
}
info := pass.TypesInfo
// check is called for each statement of this form:
// for k, v := range x { m[k] = v }
check := func(file *ast.File, curRange inspector.Cursor, assign *ast.AssignStmt, m, x ast.Expr) {
// Is x a map or iter.Seq2?
tx := types.Unalias(info.TypeOf(x))
var xmap bool
switch typeparams.CoreType(tx).(type) {
case *types.Map:
xmap = true
case *types.Signature:
k, v, ok := assignableToIterSeq2(tx)
if !ok {
return // a named isomer of Seq2
}
xmap = false
// Record in tx the unnamed map[K]V type
// derived from the yield function.
// This is the type of maps.Collect(x).
tx = types.NewMap(k, v)
default:
return // e.g. slice, channel (or no core type!)
}
// Is the preceding statement of the form
// m = make(M) or M{}
// and can we replace its RHS with slices.{Clone,Collect}?
//
// Beware: if x may be nil, we cannot use Clone as it preserves nilness.
var mrhs ast.Expr // make(M) or M{}, or nil
if curPrev, ok := curRange.PrevSibling(); ok {
if assign, ok := curPrev.Node().(*ast.AssignStmt); ok &&
len(assign.Lhs) == 1 &&
len(assign.Rhs) == 1 &&
astutil.EqualSyntax(assign.Lhs[0], m) {
// Have: m = rhs; for k, v := range x { m[k] = v }
var newMap bool
rhs := assign.Rhs[0]
switch rhs := ast.Unparen(rhs).(type) {
case *ast.CallExpr:
if id, ok := ast.Unparen(rhs.Fun).(*ast.Ident); ok &&
info.Uses[id] == builtinMake {
// Have: m = make(...)
newMap = true
}
case *ast.CompositeLit:
if len(rhs.Elts) == 0 {
// Have m = M{}
newMap = true
}
}
// Take care not to change type of m's RHS expression.
if newMap {
trhs := info.TypeOf(rhs)
// Inv: tx is the type of maps.F(x)
// - maps.Clone(x) has the same type as x.
// - maps.Collect(x) returns an unnamed map type.
if assign.Tok == token.DEFINE {
// DEFINE (:=): we must not
// change the type of RHS.
if types.Identical(tx, trhs) {
mrhs = rhs
}
} else {
// ASSIGN (=): the types of LHS
// and RHS may differ in namedness.
if types.AssignableTo(tx, trhs) {
mrhs = rhs
}
}
// Temporarily disable the transformation to the
// (nil-preserving) maps.Clone until we can prove
// that x is non-nil. This is rarely possible,
// and may require control flow analysis
// (e.g. a dominating "if len(x)" check).
// See #71844.
if xmap {
mrhs = nil
}
}
}
}
// Choose function.
var funcName string
if mrhs != nil {
funcName = cond(xmap, "Clone", "Collect")
} else {
funcName = cond(xmap, "Copy", "Insert")
}
// Report diagnostic, and suggest fix.
rng := curRange.Node()
prefix, importEdits := refactor.AddImport(info, file, "maps", "maps", funcName, rng.Pos())
var (
newText []byte
start, end token.Pos
)
if mrhs != nil {
// Replace assignment and loop with expression.
//
// m = make(...)
// for k, v := range x { /* comments */ m[k] = v }
//
// ->
//
// /* comments */
// m = maps.Copy(x)
curPrev, _ := curRange.PrevSibling()
start, end = curPrev.Node().Pos(), rng.End()
newText = fmt.Appendf(nil, "%s%s = %s%s(%s)",
allComments(file, start, end),
astutil.Format(pass.Fset, m),
prefix,
funcName,
astutil.Format(pass.Fset, x))
} else {
// Replace loop with call statement.
//
// for k, v := range x { /* comments */ m[k] = v }
//
// ->
//
// /* comments */
// maps.Copy(m, x)
start, end = rng.Pos(), rng.End()
newText = fmt.Appendf(nil, "%s%s%s(%s, %s)",
allComments(file, start, end),
prefix,
funcName,
astutil.Format(pass.Fset, m),
astutil.Format(pass.Fset, x))
}
pass.Report(analysis.Diagnostic{
Pos: assign.Lhs[0].Pos(),
End: assign.Lhs[0].End(),
Message: "Replace m[k]=v loop with maps." + funcName,
SuggestedFixes: []analysis.SuggestedFix{{
Message: "Replace m[k]=v loop with maps." + funcName,
TextEdits: append(importEdits, []analysis.TextEdit{{
Pos: start,
End: end,
NewText: newText,
}}...),
}},
})
}
// Find all range loops around m[k] = v.
for curFile := range filesUsingGoVersion(pass, versions.Go1_23) {
file := curFile.Node().(*ast.File)
for curRange := range curFile.Preorder((*ast.RangeStmt)(nil)) {
rng := curRange.Node().(*ast.RangeStmt)
if rng.Tok == token.DEFINE &&
rng.Key != nil &&
rng.Value != nil &&
isAssignBlock(rng.Body) {
// Have: for k, v := range x { lhs = rhs }
assign := rng.Body.List[0].(*ast.AssignStmt)
// usesKV reports whether e references vars k or v.
usesKV := func(e ast.Expr) bool {
k := info.Defs[rng.Key.(*ast.Ident)]
v := info.Defs[rng.Value.(*ast.Ident)]
for n := range ast.Preorder(e) {
if id, ok := n.(*ast.Ident); ok {
obj := info.Uses[id]
if obj != nil && // don't rely on k, v being non-nil
(obj == k || obj == v) {
return true
}
}
}
return false
}
if index, ok := assign.Lhs[0].(*ast.IndexExpr); ok &&
len(assign.Lhs) == 1 &&
astutil.EqualSyntax(rng.Key, index.Index) &&
astutil.EqualSyntax(rng.Value, assign.Rhs[0]) &&
!usesKV(index.X) { // reject (e.g.) f(k, v)[k] = v
if tmap, ok := typeparams.CoreType(info.TypeOf(index.X)).(*types.Map); ok &&
types.Identical(info.TypeOf(index), info.TypeOf(rng.Value)) && // m[k], v
types.Identical(tmap.Key(), info.TypeOf(rng.Key)) {
// Have: for k, v := range x { m[k] = v }
// where there is no implicit conversion
// of either key or value.
check(file, curRange, assign, index.X, rng.X)
}
}
}
}
}
return nil, nil
}
// assignableToIterSeq2 reports whether t is assignable to
// iter.Seq[K, V] and returns K and V if so.
func assignableToIterSeq2(t types.Type) (k, v types.Type, ok bool) {
// The only named type assignable to iter.Seq2 is iter.Seq2.
if is[*types.Named](t) {
if !typesinternal.IsTypeNamed(t, "iter", "Seq2") {
return
}
t = t.Underlying()
}
if t, ok := t.(*types.Signature); ok {
// func(yield func(K, V) bool)?
if t.Params().Len() == 1 && t.Results().Len() == 0 {
if yield, ok := t.Params().At(0).Type().(*types.Signature); ok { // sic, no Underlying/CoreType
if yield.Params().Len() == 2 &&
yield.Results().Len() == 1 &&
types.Identical(yield.Results().At(0).Type(), builtinBool.Type()) {
return yield.Params().At(0).Type(), yield.Params().At(1).Type(), true
}
}
}
}
return
}
================================================
FILE: go/analysis/passes/modernize/minmax.go
================================================
// Copyright 2024 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package modernize
import (
"fmt"
"go/ast"
"go/token"
"go/types"
"strings"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/ast/edge"
"golang.org/x/tools/go/ast/inspector"
"golang.org/x/tools/internal/analysis/analyzerutil"
typeindexanalyzer "golang.org/x/tools/internal/analysis/typeindex"
"golang.org/x/tools/internal/astutil"
"golang.org/x/tools/internal/typeparams"
"golang.org/x/tools/internal/typesinternal/typeindex"
"golang.org/x/tools/internal/versions"
)
var MinMaxAnalyzer = &analysis.Analyzer{
Name: "minmax",
Doc: analyzerutil.MustExtractDoc(doc, "minmax"),
Requires: []*analysis.Analyzer{
inspect.Analyzer,
typeindexanalyzer.Analyzer,
},
Run: minmax,
URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/modernize#minmax",
}
// The minmax pass replaces if/else statements with calls to min or max,
// and removes user-defined min/max functions that are equivalent to built-ins.
//
// If/else replacement patterns:
//
// 1. if a < b { x = a } else { x = b } => x = min(a, b)
// 2. x = a; if a < b { x = b } => x = max(a, b)
//
// Pattern 1 requires that a is not NaN, and pattern 2 requires that b
// is not Nan. Since this is hard to prove, we reject floating-point
// numbers.
//
// Function removal:
// User-defined min/max functions are suggested for removal if they may
// be safely replaced by their built-in namesake.
//
// Variants:
// - all four ordered comparisons
// - "x := a" or "x = a" or "var x = a" in pattern 2
// - "x < b" or "a < b" in pattern 2
func minmax(pass *analysis.Pass) (any, error) {
var (
inspect = pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
info = pass.TypesInfo
)
// Check for user-defined min/max functions that can be removed
checkUserDefinedMinMax(pass)
// check is called for all statements of this form:
// if a < b { lhs = rhs }
check := func(file *ast.File, curIfStmt inspector.Cursor, compare *ast.BinaryExpr) {
var (
ifStmt = curIfStmt.Node().(*ast.IfStmt)
tassign = ifStmt.Body.List[0].(*ast.AssignStmt)
a = compare.X
b = compare.Y
lhs = tassign.Lhs[0]
rhs = tassign.Rhs[0]
sign = isInequality(compare.Op)
// callArg formats a call argument, preserving comments from [start-end).
callArg = func(arg ast.Expr, start, end token.Pos) string {
comments := allComments(file, start, end)
return cond(arg == b, ", ", "") + // second argument needs a comma
cond(comments != "", "\n", "") + // comments need their own line
comments +
astutil.Format(pass.Fset, arg)
}
)
if fblock, ok := ifStmt.Else.(*ast.BlockStmt); ok && isAssignBlock(fblock) {
fassign := fblock.List[0].(*ast.AssignStmt)
// Have: if a < b { lhs = rhs } else { lhs2 = rhs2 }
lhs2 := fassign.Lhs[0]
rhs2 := fassign.Rhs[0]
// For pattern 1, check that:
// - lhs = lhs2
// - {rhs,rhs2} = {a,b}
if astutil.EqualSyntax(lhs, lhs2) {
if astutil.EqualSyntax(rhs, a) && astutil.EqualSyntax(rhs2, b) {
sign = +sign
} else if astutil.EqualSyntax(rhs2, a) && astutil.EqualSyntax(rhs, b) {
sign = -sign
} else {
return
}
sym := cond(sign < 0, "min", "max")
if !is[*types.Builtin](lookup(pass.TypesInfo, curIfStmt, sym)) {
return // min/max function is shadowed
}
if !analyzerutil.FileUsesGoVersion(pass, file, versions.Go1_21) {
return // min/max is too new
}
// pattern 1
//
// TODO(adonovan): if lhs is declared "var lhs T" on preceding line,
// simplify the whole thing to "lhs := min(a, b)".
pass.Report(analysis.Diagnostic{
// Highlight the condition a < b.
Pos: compare.Pos(),
End: compare.End(),
Message: fmt.Sprintf("if/else statement can be modernized using %s", sym),
SuggestedFixes: []analysis.SuggestedFix{{
Message: fmt.Sprintf("Replace if statement with %s", sym),
TextEdits: []analysis.TextEdit{{
// Replace IfStmt with lhs = min(a, b).
Pos: ifStmt.Pos(),
End: ifStmt.End(),
NewText: fmt.Appendf(nil, "%s = %s(%s%s)",
astutil.Format(pass.Fset, lhs),
sym,
callArg(a, ifStmt.Pos(), ifStmt.Else.Pos()),
callArg(b, ifStmt.Else.Pos(), ifStmt.End()),
),
}},
}},
})
}
} else if prev, ok := curIfStmt.PrevSibling(); ok && isSimpleAssign(prev.Node()) && ifStmt.Else == nil {
fassign := prev.Node().(*ast.AssignStmt)
// Have: lhs0 = rhs0; if a < b { lhs = rhs }
//
// For pattern 2, check that
// - lhs = lhs0
// - {a,b} = {rhs,rhs0} or {rhs,lhs0}
// The replacement must use rhs0 not lhs0 though.
// For example, we accept this variant:
// lhs = x; if lhs < y { lhs = y } => lhs = min(x, y), not min(lhs, y)
//
// TODO(adonovan): accept "var lhs0 = rhs0" form too.
lhs0 := fassign.Lhs[0]
rhs0 := fassign.Rhs[0]
// If the assignment occurs within a select
// comms clause (like "case lhs0 := <-rhs0:"),
// there's no way of rewriting it into a min/max call.
if prev.ParentEdgeKind() == edge.CommClause_Comm {
return
}
if astutil.EqualSyntax(lhs, lhs0) {
if astutil.EqualSyntax(rhs, a) && (astutil.EqualSyntax(rhs0, b) || astutil.EqualSyntax(lhs0, b)) {
sign = +sign
} else if (astutil.EqualSyntax(rhs0, a) || astutil.EqualSyntax(lhs0, a)) && astutil.EqualSyntax(rhs, b) {
sign = -sign
} else {
return
}
sym := cond(sign < 0, "min", "max")
if !is[*types.Builtin](lookup(pass.TypesInfo, curIfStmt, sym)) {
return // min/max function is shadowed
}
// Permit lhs0 to stand for rhs0 in the matching,
// but don't actually reduce to lhs0 = min(lhs0, rhs)
// since the "=" could be a ":=". Use min(rhs0, rhs).
if astutil.EqualSyntax(lhs0, a) {
a = rhs0
} else if astutil.EqualSyntax(lhs0, b) {
b = rhs0
}
if !analyzerutil.FileUsesGoVersion(pass, file, versions.Go1_21) {
return // min/max is too new
}
// pattern 2
pass.Report(analysis.Diagnostic{
// Highlight the condition a < b.
Pos: compare.Pos(),
End: compare.End(),
Message: fmt.Sprintf("if statement can be modernized using %s", sym),
SuggestedFixes: []analysis.SuggestedFix{{
Message: fmt.Sprintf("Replace if/else with %s", sym),
TextEdits: []analysis.TextEdit{{
Pos: fassign.Pos(),
End: ifStmt.End(),
// Replace "x := a; if ... {}" with "x = min(...)", preserving comments.
NewText: fmt.Appendf(nil, "%s %s %s(%s%s)",
astutil.Format(pass.Fset, lhs),
fassign.Tok.String(),
sym,
callArg(a, fassign.Pos(), ifStmt.Pos()),
callArg(b, ifStmt.Pos(), ifStmt.End()),
),
}},
}},
})
}
}
}
// Find all "if a < b { lhs = rhs }" statements.
for curIfStmt := range inspect.Root().Preorder((*ast.IfStmt)(nil)) {
ifStmt := curIfStmt.Node().(*ast.IfStmt)
// Don't bother handling "if a < b { lhs = rhs }" when it appears
// as the "else" branch of another if-statement.
// if cond { ... } else if a < b { lhs = rhs }
// (This case would require introducing another block
// if cond { ... } else { if a < b { lhs = rhs } }
// and checking that there is no following "else".)
if curIfStmt.ParentEdgeKind() == edge.IfStmt_Else {
continue
}
if compare, ok := ifStmt.Cond.(*ast.BinaryExpr); ok &&
ifStmt.Init == nil &&
isInequality(compare.Op) != 0 &&
isAssignBlock(ifStmt.Body) {
// a blank var has no type.
if tLHS := info.TypeOf(ifStmt.Body.List[0].(*ast.AssignStmt).Lhs[0]); tLHS != nil && !maybeNaN(tLHS) {
// Have: if a < b { lhs = rhs }
check(astutil.EnclosingFile(curIfStmt), curIfStmt, compare)
}
}
}
return nil, nil
}
// allComments collects all the comments from start to end.
func allComments(file *ast.File, start, end token.Pos) string {
var buf strings.Builder
for co := range astutil.Comments(file, start, end) {
_, _ = fmt.Fprintf(&buf, "%s\n", co.Text)
}
return buf.String()
}
// isInequality reports non-zero if tok is one of < <= => >:
// +1 for > and -1 for <.
func isInequality(tok token.Token) int {
switch tok {
case token.LEQ, token.LSS:
return -1
case token.GEQ, token.GTR:
return +1
}
return 0
}
// isAssignBlock reports whether b is a block of the form { lhs = rhs }.
func isAssignBlock(b *ast.BlockStmt) bool {
if len(b.List) != 1 {
return false
}
// Inv: the sole statement cannot be { lhs := rhs }.
return isSimpleAssign(b.List[0])
}
// isSimpleAssign reports whether n has the form "lhs = rhs" or "lhs := rhs".
func isSimpleAssign(n ast.Node) bool {
assign, ok := n.(*ast.AssignStmt)
return ok &&
(assign.Tok == token.ASSIGN || assign.Tok == token.DEFINE) &&
len(assign.Lhs) == 1 &&
len(assign.Rhs) == 1
}
// maybeNaN reports whether t is (or may be) a floating-point type.
func maybeNaN(t types.Type) bool {
// For now, we rely on core types.
// TODO(adonovan): In the post-core-types future,
// follow the approach of types.Checker.applyTypeFunc.
t = typeparams.CoreType(t)
if t == nil {
return true // fail safe
}
if basic, ok := t.(*types.Basic); ok && basic.Info()&types.IsFloat != 0 {
return true
}
return false
}
// checkUserDefinedMinMax looks for user-defined min/max functions that are
// equivalent to the built-in functions and suggests removing them.
func checkUserDefinedMinMax(pass *analysis.Pass) {
index := pass.ResultOf[typeindexanalyzer.Analyzer].(*typeindex.Index)
// Look up min and max functions by name in package scope
for _, funcName := range []string{"min", "max"} {
if fn, ok := pass.Pkg.Scope().Lookup(funcName).(*types.Func); ok {
// Use typeindex to get the FuncDecl directly
if def, ok := index.Def(fn); ok {
decl := def.Parent().Node().(*ast.FuncDecl)
// Check if this function matches the built-in min/max signature
// and behavior, and verify that we have go1.21.
if canUseBuiltinMinMax(fn, decl.Body) &&
analyzerutil.FileUsesGoVersion(pass, astutil.EnclosingFile(def), versions.Go1_21) {
// Expand to include leading doc comment
pos := decl.Pos()
if docs := astutil.DocComment(decl); docs != nil {
pos = docs.Pos()
}
pass.Report(analysis.Diagnostic{
Pos: decl.Pos(),
End: decl.End(),
Message: fmt.Sprintf("user-defined %s function is equivalent to built-in %s and can be removed", funcName, funcName),
SuggestedFixes: []analysis.SuggestedFix{{
Message: fmt.Sprintf("Remove user-defined %s function", funcName),
TextEdits: []analysis.TextEdit{{
Pos: pos,
End: decl.End(),
}},
}},
})
}
}
}
}
}
// canUseBuiltinMinMax reports whether it is safe to replace a call
// to this min or max function by its built-in namesake.
func canUseBuiltinMinMax(fn *types.Func, body *ast.BlockStmt) bool {
sig := fn.Type().(*types.Signature)
// Only consider the most common case: exactly 2 parameters
if sig.Params().Len() != 2 {
return false
}
// Check if any parameter might be floating-point
for param := range sig.Params().Variables() {
if maybeNaN(param.Type()) {
return false // Don't suggest removal for float types due to NaN handling
}
}
// Must have exactly one return value
if sig.Results().Len() != 1 {
return false
}
// Check that the function body implements the expected min/max logic
if body == nil {
return false
}
return hasMinMaxLogic(body, fn.Name())
}
// hasMinMaxLogic checks if the function body implements simple min/max logic.
func hasMinMaxLogic(body *ast.BlockStmt, funcName string) bool {
// Pattern 1: Single if/else statement
if len(body.List) == 1 {
if ifStmt, ok := body.List[0].(*ast.IfStmt); ok {
// Get the "false" result from the else block
if elseBlock, ok := ifStmt.Else.(*ast.BlockStmt); ok && len(elseBlock.List) == 1 {
if elseRet, ok := elseBlock.List[0].(*ast.ReturnStmt); ok && len(elseRet.Results) == 1 {
return checkMinMaxPattern(ifStmt, elseRet.Results[0], funcName)
}
}
}
}
// Pattern 2: if statement followed by return
if len(body.List) == 2 {
if ifStmt, ok := body.List[0].(*ast.IfStmt); ok && ifStmt.Else == nil {
if retStmt, ok := body.List[1].(*ast.ReturnStmt); ok && len(retStmt.Results) == 1 {
return checkMinMaxPattern(ifStmt, retStmt.Results[0], funcName)
}
}
}
return false
}
// checkMinMaxPattern checks if an if statement implements min/max logic.
// ifStmt: the if statement to check
// falseResult: the expression returned when the condition is false
// funcName: "min" or "max"
func checkMinMaxPattern(ifStmt *ast.IfStmt, falseResult ast.Expr, funcName string) bool {
// Must have condition with comparison
cmp, ok := ifStmt.Cond.(*ast.BinaryExpr)
if !ok {
return false
}
// Check if then branch returns one of the compared values
if len(ifStmt.Body.List) != 1 {
return false
}
thenRet, ok := ifStmt.Body.List[0].(*ast.ReturnStmt)
if !ok || len(thenRet.Results) != 1 {
return false
}
// Use the same logic as the existing minmax analyzer
sign := isInequality(cmp.Op)
if sign == 0 {
return false // Not a comparison operator
}
t := thenRet.Results[0] // "true" result
f := falseResult // "false" result
x := cmp.X // left operand
y := cmp.Y // right operand
// Check operand order and adjust sign accordingly
if astutil.EqualSyntax(t, x) && astutil.EqualSyntax(f, y) {
sign = +sign
} else if astutil.EqualSyntax(t, y) && astutil.EqualSyntax(f, x) {
sign = -sign
} else {
return false
}
// Check if the sign matches the function name
return cond(sign < 0, "min", "max") == funcName
}
// -- utils --
func is[T any](x any) bool {
_, ok := x.(T)
return ok
}
func cond[T any](cond bool, t, f T) T {
if cond {
return t
} else {
return f
}
}
================================================
FILE: go/analysis/passes/modernize/modernize.go
================================================
// Copyright 2024 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package modernize
import (
_ "embed"
"go/ast"
"go/constant"
"go/format"
"go/token"
"go/types"
"iter"
"regexp"
"strings"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/ast/edge"
"golang.org/x/tools/go/ast/inspector"
"golang.org/x/tools/internal/analysis/analyzerutil"
"golang.org/x/tools/internal/refactor"
"golang.org/x/tools/internal/typesinternal/typeindex"
"golang.org/x/tools/internal/moreiters"
"golang.org/x/tools/internal/packagepath"
"golang.org/x/tools/internal/stdlib"
"golang.org/x/tools/internal/typesinternal"
)
//go:embed doc.go
var doc string
// Suite lists all modernize analyzers.
var Suite = []*analysis.Analyzer{
AnyAnalyzer,
atomicTypesAnalyzer,
// AppendClippedAnalyzer, // not nil-preserving!
// BLoopAnalyzer, // may skew benchmark results, see golang/go#74967
FmtAppendfAnalyzer,
ForVarAnalyzer,
MapsLoopAnalyzer,
MinMaxAnalyzer,
NewExprAnalyzer,
OmitZeroAnalyzer,
plusBuildAnalyzer,
RangeIntAnalyzer,
ReflectTypeForAnalyzer,
SlicesContainsAnalyzer,
// SlicesDeleteAnalyzer, // not nil-preserving!
SlicesSortAnalyzer,
stditeratorsAnalyzer,
stringscutAnalyzer,
StringsCutPrefixAnalyzer,
StringsSeqAnalyzer,
StringsBuilderAnalyzer,
TestingContextAnalyzer,
unsafeFuncsAnalyzer,
WaitGroupGoAnalyzer,
}
// -- helpers --
// formatExprs formats a comma-separated list of expressions.
func formatExprs(fset *token.FileSet, exprs []ast.Expr) string {
var buf strings.Builder
for i, e := range exprs {
if i > 0 {
buf.WriteString(", ")
}
format.Node(&buf, fset, e) // ignore errors
}
return buf.String()
}
// isZeroIntConst reports whether e is an integer whose value is 0.
func isZeroIntConst(info *types.Info, e ast.Expr) bool {
return isIntLiteral(info, e, 0)
}
// isIntLiteral reports whether e is an integer with given value.
func isIntLiteral(info *types.Info, e ast.Expr, n int64) bool {
return info.Types[e].Value == constant.MakeInt64(n)
}
// filesUsingGoVersion returns a cursor for each *ast.File in the inspector
// that uses at least the specified version of Go (e.g. "go1.24").
//
// The pass's analyzer must require [inspect.Analyzer].
//
// TODO(adonovan): opt: eliminate this function, instead following the
// approach of [fmtappendf], which uses typeindex and
// [analyzerutil.FileUsesGoVersion]; see "Tip" documented at the
// latter function for motivation.
func filesUsingGoVersion(pass *analysis.Pass, version string) iter.Seq[inspector.Cursor] {
inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
return func(yield func(inspector.Cursor) bool) {
for curFile := range inspect.Root().Children() {
file := curFile.Node().(*ast.File)
if analyzerutil.FileUsesGoVersion(pass, file, version) && !yield(curFile) {
break
}
}
}
}
// within reports whether the current pass is analyzing one of the
// specified standard packages or their dependencies.
func within(pass *analysis.Pass, pkgs ...string) bool {
path := pass.Pkg.Path()
return packagepath.IsStdPackage(path) &&
moreiters.Contains(stdlib.Dependencies(pkgs...), path)
}
// unparenEnclosing removes enclosing parens from cur in
// preparation for a call to [Cursor.ParentEdge].
func unparenEnclosing(cur inspector.Cursor) inspector.Cursor {
for cur.ParentEdgeKind() == edge.ParenExpr_X {
cur = cur.Parent()
}
return cur
}
var (
builtinAny = types.Universe.Lookup("any")
builtinAppend = types.Universe.Lookup("append")
builtinBool = types.Universe.Lookup("bool")
builtinInt = types.Universe.Lookup("int")
builtinFalse = types.Universe.Lookup("false")
builtinLen = types.Universe.Lookup("len")
builtinMake = types.Universe.Lookup("make")
builtinNew = types.Universe.Lookup("new")
builtinNil = types.Universe.Lookup("nil")
builtinString = types.Universe.Lookup("string")
builtinTrue = types.Universe.Lookup("true")
byteSliceType = types.NewSlice(types.Typ[types.Byte])
omitemptyRegex = regexp.MustCompile(`(?:^json| json):"[^"]*(,omitempty)(?:"|,[^"]*")\s?`)
)
// lookup returns the symbol denoted by name at the position of the cursor.
func lookup(info *types.Info, cur inspector.Cursor, name string) types.Object {
scope := typesinternal.EnclosingScope(info, cur)
_, obj := scope.LookupParent(name, cur.Node().Pos())
return obj
}
func first[T any](x T, _ any) T { return x }
// freshName returns a fresh name at the given pos and scope based on preferredName.
// It generates a new name using refactor.FreshName only if:
// (a) the preferred name is already defined at definedCur, and
// (b) there are references to it from within usedCur.
// If useAfterPos.IsValid(), the references must be after
// useAfterPos within usedCur in order to warrant a fresh name.
// Otherwise, it returns preferredName, since shadowing is valid in this case.
// (declaredCur and usedCur may be identical in some use cases).
func freshName(info *types.Info, index *typeindex.Index, scope *types.Scope, pos token.Pos, defCur inspector.Cursor, useCur inspector.Cursor, useAfterPos token.Pos, preferredName string) string {
obj := lookup(info, defCur, preferredName)
if obj == nil {
// preferredName has not been declared here.
return preferredName
}
for use := range index.Uses(obj) {
if useCur.Contains(use) && use.Node().Pos() >= useAfterPos {
return refactor.FreshName(scope, pos, preferredName)
}
}
// Name is taken but not used in the given block; shadowing is acceptable.
return preferredName
}
// isLocal reports whether obj is local to some function.
// Precondition: not a struct field or interface method.
func isLocal(obj types.Object) bool {
// [... 5=stmt 4=func 3=file 2=pkg 1=universe]
var depth int
for scope := obj.Parent(); scope != nil; scope = scope.Parent() {
depth++
}
return depth >= 4
}
================================================
FILE: go/analysis/passes/modernize/modernize_test.go
================================================
// Copyright 2024 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package modernize_test
import (
"testing"
. "golang.org/x/tools/go/analysis/analysistest"
"golang.org/x/tools/go/analysis/passes/modernize"
"golang.org/x/tools/internal/goplsexport"
"golang.org/x/tools/internal/testenv"
)
func TestAppendClipped(t *testing.T) {
RunWithSuggestedFixes(t, TestData(), modernize.AppendClippedAnalyzer, "appendclipped")
}
func TestAtomicTypes(t *testing.T) {
RunWithSuggestedFixes(t, TestData(), goplsexport.AtomicTypesModernizer, "atomictypes/...")
}
func TestBloop(t *testing.T) {
RunWithSuggestedFixes(t, TestData(), modernize.BLoopAnalyzer, "bloop")
}
func TestAny(t *testing.T) {
// The 'any' tests also exercise that fixes are not applied to generated files.
RunWithSuggestedFixes(t, TestData(), modernize.AnyAnalyzer, "any")
}
func TestErrorsAsType(t *testing.T) {
RunWithSuggestedFixes(t, TestData(), goplsexport.ErrorsAsTypeModernizer, "errorsastype/...")
}
func TestFmtAppendf(t *testing.T) {
RunWithSuggestedFixes(t, TestData(), modernize.FmtAppendfAnalyzer, "fmtappendf")
}
func TestForVar(t *testing.T) {
RunWithSuggestedFixes(t, TestData(), modernize.ForVarAnalyzer, "forvar")
}
func TestStdIterators(t *testing.T) {
RunWithSuggestedFixes(t, TestData(), goplsexport.StdIteratorsModernizer, "stditerators")
}
func TestMapsLoop(t *testing.T) {
RunWithSuggestedFixes(t, TestData(), modernize.MapsLoopAnalyzer, "mapsloop")
}
func TestMinMax(t *testing.T) {
RunWithSuggestedFixes(t, TestData(), modernize.MinMaxAnalyzer, "minmax", "minmax/userdefined", "minmax/wrongoperators", "minmax/nonstrict", "minmax/wrongreturn", "minmax/go120")
}
func TestNewExpr(t *testing.T) {
RunWithSuggestedFixes(t, TestData(), modernize.NewExprAnalyzer, "newexpr")
}
func TestOmitZero(t *testing.T) {
RunWithSuggestedFixes(t, TestData(), modernize.OmitZeroAnalyzer, "omitzero/...")
}
func TestRangeInt(t *testing.T) {
RunWithSuggestedFixes(t, TestData(), modernize.RangeIntAnalyzer, "rangeint")
}
func TestPlusBuild(t *testing.T) {
// This test assumes a particular OS/ARCH so that the test
// files are selected by the build; otherwise they would
// appear in IgnoredFiles, for which file version information
// is not available. See https://go.dev/issue/77318.
t.Setenv("GOOS", "linux")
t.Setenv("GOARCH", "amd64")
// This test has a dedicated hack in the analysistest package:
// Because it cares about IgnoredFiles, which most analyzers
// ignore, the test framework will consider expectations in
// ignore files too, but only for this analyzer.
// (But see above: IgnoredFiles lack version information
// so we can't safely apply any fixes to them.)
RunWithSuggestedFixes(t, TestData(), goplsexport.PlusBuildModernizer, "plusbuild")
}
func TestReflectTypeFor(t *testing.T) {
testenv.NeedsGo1Point(t, 25) // requires go1.25 types.Var.Kind
RunWithSuggestedFixes(t, TestData(), modernize.ReflectTypeForAnalyzer, "reflecttypefor")
}
func TestSlicesContains(t *testing.T) {
RunWithSuggestedFixes(t, TestData(), modernize.SlicesContainsAnalyzer, "slicescontains")
}
func TestSlicesDelete(t *testing.T) {
RunWithSuggestedFixes(t, TestData(), modernize.SlicesDeleteAnalyzer, "slicesdelete")
}
func TestSlicesSort(t *testing.T) {
RunWithSuggestedFixes(t, TestData(), modernize.SlicesSortAnalyzer, "slicessort")
}
func TestStringsBuilder(t *testing.T) {
RunWithSuggestedFixes(t, TestData(), modernize.StringsBuilderAnalyzer, "stringsbuilder")
}
func TestStringsCut(t *testing.T) {
RunWithSuggestedFixes(t, TestData(), goplsexport.StringsCutModernizer, "stringscut")
}
func TestStringsCutPrefix(t *testing.T) {
RunWithSuggestedFixes(t, TestData(), modernize.StringsCutPrefixAnalyzer,
"stringscutprefix",
"stringscutprefix/bytescutprefix")
}
func TestStringsSeq(t *testing.T) {
RunWithSuggestedFixes(t, TestData(), modernize.StringsSeqAnalyzer, "splitseq", "fieldsseq")
}
func TestTestingContext(t *testing.T) {
RunWithSuggestedFixes(t, TestData(), modernize.TestingContextAnalyzer, "testingcontext")
}
func TestUnsafeFuncs(t *testing.T) {
RunWithSuggestedFixes(t, TestData(), goplsexport.UnsafeFuncsModernizer, "unsafefuncs")
}
func TestWaitGroupGo(t *testing.T) {
RunWithSuggestedFixes(t, TestData(), modernize.WaitGroupGoAnalyzer, "waitgroupgo")
}
================================================
FILE: go/analysis/passes/modernize/newexpr.go
================================================
// Copyright 2025 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package modernize
import (
_ "embed"
"fmt"
"go/ast"
"go/token"
"go/types"
"slices"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/ast/inspector"
"golang.org/x/tools/go/types/typeutil"
"golang.org/x/tools/internal/analysis/analyzerutil"
"golang.org/x/tools/internal/astutil"
"golang.org/x/tools/internal/versions"
)
var NewExprAnalyzer = &analysis.Analyzer{
Name: "newexpr",
Doc: analyzerutil.MustExtractDoc(doc, "newexpr"),
URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/modernize#newexpr",
Requires: []*analysis.Analyzer{inspect.Analyzer},
Run: run,
FactTypes: []analysis.Fact{&newLike{}},
}
func run(pass *analysis.Pass) (any, error) {
var (
inspect = pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
info = pass.TypesInfo
)
// Detect functions that are new-like, i.e. have the form:
//
// func f(x T) *T { return &x }
//
// meaning that it is equivalent to new(x), if x has type T.
for curFuncDecl := range inspect.Root().Preorder((*ast.FuncDecl)(nil)) {
decl := curFuncDecl.Node().(*ast.FuncDecl)
fn := info.Defs[decl.Name].(*types.Func)
if decl.Body != nil && len(decl.Body.List) == 1 {
if ret, ok := decl.Body.List[0].(*ast.ReturnStmt); ok && len(ret.Results) == 1 {
if unary, ok := ret.Results[0].(*ast.UnaryExpr); ok && unary.Op == token.AND {
if id, ok := unary.X.(*ast.Ident); ok {
if v, ok := info.Uses[id].(*types.Var); ok {
sig := fn.Signature()
if sig.Results().Len() == 1 &&
is[*types.Pointer](sig.Results().At(0).Type()) && // => no iface conversion
sig.Params().Len() == 1 &&
sig.Params().At(0) == v {
// Export a fact for each one.
pass.ExportObjectFact(fn, &newLike{})
// Check file version.
file := astutil.EnclosingFile(curFuncDecl)
if !analyzerutil.FileUsesGoVersion(pass, file, versions.Go1_26) {
continue // new(expr) not available in this file
}
var edits []analysis.TextEdit
// If 'new' is not shadowed, replace func body: &x -> new(x).
// This makes it safely and cleanly inlinable.
curRet, _ := curFuncDecl.FindNode(ret)
if lookup(info, curRet, "new") == builtinNew {
edits = []analysis.TextEdit{
// return &x
// ---- -
// return new(x)
{
Pos: unary.OpPos,
End: unary.OpPos + token.Pos(len("&")),
NewText: []byte("new("),
},
{
Pos: unary.X.End(),
End: unary.X.End(),
NewText: []byte(")"),
},
}
}
// Add a //go:fix inline annotation, if not already present.
//
// The inliner will not inline a newer callee body into an
// older Go file; see https://go.dev/issue/75726.
//
// TODO(adonovan): use ast.ParseDirective when go1.26 is assured.
if !slices.ContainsFunc(astutil.Directives(decl.Doc), func(d *astutil.Directive) bool {
return d.Tool == "go" && d.Name == "fix" && d.Args == "inline"
}) {
edits = append(edits, analysis.TextEdit{
Pos: decl.Pos(),
End: decl.Pos(),
NewText: []byte("//go:fix inline\n"),
})
}
if len(edits) > 0 {
pass.Report(analysis.Diagnostic{
Pos: decl.Name.Pos(),
End: decl.Name.End(),
Message: fmt.Sprintf("%s can be an inlinable wrapper around new(expr)", decl.Name),
SuggestedFixes: []analysis.SuggestedFix{
{
Message: "Make %s an inlinable wrapper around new(expr)",
TextEdits: edits,
},
},
})
}
}
}
}
}
}
}
}
// Report and transform calls, when safe.
// In effect, this is inlining the new-like function
// even before we have marked the callee with //go:fix inline.
for curCall := range inspect.Root().Preorder((*ast.CallExpr)(nil)) {
call := curCall.Node().(*ast.CallExpr)
var fact newLike
if fn, ok := typeutil.Callee(info, call).(*types.Func); ok &&
pass.ImportObjectFact(fn, &fact) {
// Check file version.
file := astutil.EnclosingFile(curCall)
if !analyzerutil.FileUsesGoVersion(pass, file, versions.Go1_26) {
continue // new(expr) not available in this file
}
// Check new is not shadowed.
if lookup(info, curCall, "new") != builtinNew {
continue
}
// The return type *T must exactly match the argument type T.
// (We formulate it this way--not in terms of the parameter
// type--to support generics.)
var targ types.Type
{
arg := call.Args[0]
tvarg := info.Types[arg]
// Constants: we must work around the type checker
// bug that causes info.Types to wrongly report the
// "typed" type for an untyped constant.
// (See "historical reasons" in issue go.dev/issue/70638.)
//
// We don't have a reliable way to do this but we can attempt
// to re-typecheck the constant expression on its own, in
// the original lexical environment but not as a part of some
// larger expression that implies a conversion to some "typed" type.
// (For the genesis of this idea see (*state).arguments
// in ../../../../internal/refactor/inline/inline.go.)
if tvarg.Value != nil {
info2 := &types.Info{Types: make(map[ast.Expr]types.TypeAndValue)}
if err := types.CheckExpr(token.NewFileSet(), pass.Pkg, token.NoPos, arg, info2); err != nil {
continue // unexpected error
}
tvarg = info2.Types[arg]
}
targ = types.Default(tvarg.Type)
}
if !types.Identical(types.NewPointer(targ), info.TypeOf(call)) {
continue
}
pass.Report(analysis.Diagnostic{
Pos: call.Pos(),
End: call.End(),
Message: fmt.Sprintf("call of %s(x) can be simplified to new(x)", fn.Name()),
SuggestedFixes: []analysis.SuggestedFix{{
Message: fmt.Sprintf("Simplify %s(x) to new(x)", fn.Name()),
TextEdits: []analysis.TextEdit{{
Pos: call.Fun.Pos(),
End: call.Fun.End(),
NewText: []byte("new"),
}},
}},
})
}
}
return nil, nil
}
// A newLike fact records that its associated function is "new-like".
type newLike struct{}
func (*newLike) AFact() {}
func (*newLike) String() string { return "newlike" }
================================================
FILE: go/analysis/passes/modernize/omitzero.go
================================================
// Copyright 2025 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package modernize
import (
"go/ast"
"go/types"
"reflect"
"strconv"
"strings"
"sync"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/internal/analysis/analyzerutil"
"golang.org/x/tools/internal/astutil"
"golang.org/x/tools/internal/versions"
)
var OmitZeroAnalyzer = &analysis.Analyzer{
Name: "omitzero",
Doc: analyzerutil.MustExtractDoc(doc, "omitzero"),
Requires: []*analysis.Analyzer{inspect.Analyzer},
Run: omitzero,
URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/modernize#omitzero",
}
// The omitzero pass searches for instances of "omitempty" in a json field tag on a
// struct. Since "omitfilesUsingGoVersions not have any effect when applied to a struct field,
// it suggests either deleting "omitempty" or replacing it with "omitzero", which
// correctly excludes structs from a json encoding.
func omitzero(pass *analysis.Pass) (any, error) {
// usesKubebuilder reports whether "+kubebuilder:" appears in
// any comment in the package, since it has its own
// interpretation of what omitzero means; see go.dev/issue/76649.
// It is computed once, on demand.
usesKubebuilder := sync.OnceValue[bool](func() bool {
for _, file := range pass.Files {
for _, comment := range file.Comments {
if strings.Contains(comment.Text(), "+kubebuilder:") {
return true
}
}
}
return false
})
checkField := func(field *ast.Field) {
typ := pass.TypesInfo.TypeOf(field.Type)
_, ok := typ.Underlying().(*types.Struct)
if !ok {
// Not a struct
return
}
tag := field.Tag
if tag == nil {
// No tag to check
return
}
// The omitempty tag may be used by other packages besides json, but we should only modify its use with json
tagconv, _ := strconv.Unquote(tag.Value)
match := omitemptyRegex.FindStringSubmatchIndex(tagconv)
if match == nil {
// No omitempty in json tag
return
}
omitEmpty, err := astutil.RangeInStringLiteral(field.Tag, match[2], match[3])
if err != nil {
return
}
var remove analysis.Range = omitEmpty
jsonTag := reflect.StructTag(tagconv).Get("json")
if jsonTag == ",omitempty" {
// Remove the entire struct tag if json is the only package used
if match[1]-match[0] == len(tagconv) {
remove = field.Tag
} else {
// Remove the json tag if omitempty is the only field
remove, err = astutil.RangeInStringLiteral(field.Tag, match[0], match[1])
if err != nil {
return
}
}
}
// Don't offer a fix if the package seems to use kubebuilder,
// as it has its own intepretation of "omitzero" tags.
// https://book.kubebuilder.io/reference/markers.html
if usesKubebuilder() {
return
}
pass.Report(analysis.Diagnostic{
Pos: field.Tag.Pos(),
End: field.Tag.End(),
Message: "Omitempty has no effect on nested struct fields",
SuggestedFixes: []analysis.SuggestedFix{
{
Message: "Remove redundant omitempty tag",
TextEdits: []analysis.TextEdit{
{
Pos: remove.Pos(),
End: remove.End(),
},
},
},
{
Message: "Replace omitempty with omitzero (behavior change)",
TextEdits: []analysis.TextEdit{
{
Pos: omitEmpty.Pos(),
End: omitEmpty.End(),
NewText: []byte(",omitzero"),
},
},
},
}})
}
for curFile := range filesUsingGoVersion(pass, versions.Go1_24) {
for curStruct := range curFile.Preorder((*ast.StructType)(nil)) {
for _, curField := range curStruct.Node().(*ast.StructType).Fields.List {
checkField(curField)
}
}
}
return nil, nil
}
================================================
FILE: go/analysis/passes/modernize/plusbuild.go
================================================
// Copyright 2025 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package modernize
import (
"go/ast"
"go/parser"
"strings"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/internal/analysis/analyzerutil"
"golang.org/x/tools/internal/goplsexport"
"golang.org/x/tools/internal/versions"
)
var plusBuildAnalyzer = &analysis.Analyzer{
Name: "plusbuild",
Doc: analyzerutil.MustExtractDoc(doc, "plusbuild"),
URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/modernize#plusbuild",
Run: plusbuild,
}
func init() {
// Export to gopls until this is a published modernizer.
goplsexport.PlusBuildModernizer = plusBuildAnalyzer
}
func plusbuild(pass *analysis.Pass) (any, error) {
check := func(f *ast.File) {
// "//go:build" directives were added in go1.17, but
// we didn't start eliminating +build directives till go1.18.
if !analyzerutil.FileUsesGoVersion(pass, f, versions.Go1_18) {
return
}
// When gofmt sees a +build comment, it adds a
// preceding equivalent //go:build directive, so in
// formatted files we can assume that a +build line is
// part of a comment group that starts with a
// //go:build line and is followed by a blank line.
//
// While we cannot delete comments from an AST and
// expect consistent output in general, this specific
// case--deleting only some lines from a comment
// block--does format correctly.
for _, g := range f.Comments {
sawGoBuild := false
for _, c := range g.List {
if sawGoBuild && strings.HasPrefix(c.Text, "// +build ") {
pass.Report(analysis.Diagnostic{
Pos: c.Pos(),
End: c.End(),
Message: "+build line is no longer needed",
SuggestedFixes: []analysis.SuggestedFix{{
Message: "Remove obsolete +build line",
TextEdits: []analysis.TextEdit{{
Pos: c.Pos(),
End: c.End(),
}},
}},
})
break
}
if strings.HasPrefix(c.Text, "//go:build ") {
sawGoBuild = true
}
}
}
}
for _, f := range pass.Files {
check(f)
}
for _, name := range pass.IgnoredFiles {
if strings.HasSuffix(name, ".go") {
f, err := parser.ParseFile(pass.Fset, name, nil, parser.ParseComments|parser.SkipObjectResolution)
if err != nil {
continue // parse error: ignore
}
check(f)
}
}
return nil, nil
}
================================================
FILE: go/analysis/passes/modernize/rangeint.go
================================================
// Copyright 2025 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package modernize
import (
"fmt"
"go/ast"
"go/token"
"go/types"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/ast/edge"
"golang.org/x/tools/go/ast/inspector"
"golang.org/x/tools/go/types/typeutil"
"golang.org/x/tools/internal/analysis/analyzerutil"
typeindexanalyzer "golang.org/x/tools/internal/analysis/typeindex"
"golang.org/x/tools/internal/astutil"
"golang.org/x/tools/internal/typesinternal"
"golang.org/x/tools/internal/typesinternal/typeindex"
"golang.org/x/tools/internal/versions"
)
var RangeIntAnalyzer = &analysis.Analyzer{
Name: "rangeint",
Doc: analyzerutil.MustExtractDoc(doc, "rangeint"),
Requires: []*analysis.Analyzer{
inspect.Analyzer,
typeindexanalyzer.Analyzer,
},
Run: rangeint,
URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/modernize#rangeint",
}
// rangeint offers a fix to replace a 3-clause 'for' loop:
//
// for i := 0; i < limit; i++ {}
//
// by a range loop with an integer operand:
//
// for i := range limit {}
//
// Variants:
// - The ':=' may be replaced by '='.
// - The fix may remove "i :=" if it would become unused.
//
// Restrictions:
// - The variable i must not be assigned or address-taken within the
// loop, because a "for range int" loop does not respect assignments
// to the loop index.
// - The limit must not be b.N, to avoid redundancy with bloop's fixes.
//
// Caveats:
//
// The fix causes the limit expression to be evaluated exactly once,
// instead of once per iteration. So, to avoid changing the
// cardinality of side effects, the limit expression must not involve
// function calls (e.g. seq.Len()) or channel receives. Moreover, the
// value of the limit expression must be loop invariant, which in
// practice means it must take one of the following forms:
//
// - a local variable that is assigned only once and not address-taken;
// - a constant; or
// - len(s), where s has the above properties.
func rangeint(pass *analysis.Pass) (any, error) {
var (
info = pass.TypesInfo
typeindex = pass.ResultOf[typeindexanalyzer.Analyzer].(*typeindex.Index)
)
for curFile := range filesUsingGoVersion(pass, versions.Go1_22) {
nextLoop:
for curLoop := range curFile.Preorder((*ast.ForStmt)(nil)) {
loop := curLoop.Node().(*ast.ForStmt)
if init, ok := loop.Init.(*ast.AssignStmt); ok &&
isSimpleAssign(init) &&
is[*ast.Ident](init.Lhs[0]) &&
isZeroIntConst(info, init.Rhs[0]) {
// Have: for i = 0; ... (or i := 0)
index := init.Lhs[0].(*ast.Ident)
if compare, ok := loop.Cond.(*ast.BinaryExpr); ok &&
compare.Op == token.LSS &&
astutil.EqualSyntax(compare.X, init.Lhs[0]) {
// Have: for i = 0; i < limit; ... {}
limit := compare.Y
// If limit is "len(slice)", simplify it to "slice".
//
// (Don't replace "for i := 0; i < len(map); i++"
// with "for range m" because it's too hard to prove
// that len(m) is loop-invariant).
if call, ok := limit.(*ast.CallExpr); ok &&
typeutil.Callee(info, call) == builtinLen &&
is[*types.Slice](info.TypeOf(call.Args[0]).Underlying()) {
limit = call.Args[0]
}
// Check the form of limit: must be a constant,
// or a local var that is not assigned or address-taken.
limitOK := false
if info.Types[limit].Value != nil {
limitOK = true // constant
} else if id, ok := limit.(*ast.Ident); ok {
if v, ok := info.Uses[id].(*types.Var); ok &&
!(v.Exported() && typesinternal.IsPackageLevel(v)) {
// limit is a local or unexported global var.
// (An exported global may have uses we can't see.)
for cur := range typeindex.Uses(v) {
if isScalarLvalue(info, cur) {
// Limit var is assigned or address-taken.
continue nextLoop
}
}
limitOK = true
}
}
if !limitOK {
continue nextLoop
}
validIncrement := false
if inc, ok := loop.Post.(*ast.IncDecStmt); ok &&
inc.Tok == token.INC &&
astutil.EqualSyntax(compare.X, inc.X) {
// Have: i++
validIncrement = true
} else if assign, ok := loop.Post.(*ast.AssignStmt); ok &&
assign.Tok == token.ADD_ASSIGN &&
len(assign.Rhs) == 1 && isIntLiteral(info, assign.Rhs[0], 1) &&
len(assign.Lhs) == 1 && astutil.EqualSyntax(compare.X, assign.Lhs[0]) {
// Have: i += 1
validIncrement = true
}
if validIncrement {
// Have: for i = 0; i < limit; i++ {}
// Find references to i within the loop body.
v := info.ObjectOf(index).(*types.Var)
switch v.Kind() {
case types.PackageVar:
continue nextLoop
case types.ResultVar:
// If v is a named result, it is implicitly
// used after the loop (go.dev/issue/76880).
continue nextLoop
}
used := false
for curId := range curLoop.Child(loop.Body).Preorder((*ast.Ident)(nil)) {
id := curId.Node().(*ast.Ident)
if info.Uses[id] == v {
used = true
// Reject if any is an l-value (assigned or address-taken):
// a "for range int" loop does not respect assignments to
// the loop variable.
if isScalarLvalue(info, curId) {
continue nextLoop
}
}
}
// If i is no longer used, delete "i := ".
var edits []analysis.TextEdit
if !used && init.Tok == token.DEFINE {
edits = append(edits, analysis.TextEdit{
Pos: index.Pos(),
End: init.Rhs[0].Pos(),
})
}
// If i is used after the loop,
// don't offer a fix, as a range loop
// leaves i with a different final value (limit-1).
if init.Tok == token.ASSIGN {
// Find the nearest ancestor that is not a label.
// Otherwise, checking for i usage outside of a for
// loop might not function properly further below.
// This is because the i usage might be a child of
// the loop's parent's parent, for example:
// var i int
// Loop:
// for i = 0; i < 10; i++ { break loop }
// // i is in the sibling of the label, not the loop
// fmt.Println(i)
//
ancestor := curLoop.Parent()
for is[*ast.LabeledStmt](ancestor.Node()) {
ancestor = ancestor.Parent()
}
for curId := range ancestor.Preorder((*ast.Ident)(nil)) {
id := curId.Node().(*ast.Ident)
if info.Uses[id] == v {
// Is i used after loop?
if id.Pos() > loop.End() {
continue nextLoop
}
// Is i used within a defer statement
// that is within the scope of i?
// var i int
// defer func() { print(i)}
// for i = ... { ... }
for curDefer := range curId.Enclosing((*ast.DeferStmt)(nil)) {
if curDefer.Node().Pos() > v.Pos() {
continue nextLoop
}
}
}
}
}
// If limit is len(slice),
// simplify "range len(slice)" to "range slice".
if call, ok := limit.(*ast.CallExpr); ok &&
typeutil.Callee(info, call) == builtinLen &&
is[*types.Slice](info.TypeOf(call.Args[0]).Underlying()) {
limit = call.Args[0]
}
// If the limit is a untyped constant of non-integer type,
// such as "const limit = 1e3", its effective type may
// differ between the two forms.
// In a for loop, it must be comparable with int i,
// for i := 0; i < limit; i++ {}
// but in a range loop it would become a float,
// for i := range limit {}
// which is a type error. We need to convert it to int
// in this case.
//
// Unfortunately go/types discards the untyped type
// (but see Untyped in golang/go#70638) so we must
// re-type check the expression to detect this case.
var beforeLimit, afterLimit string
if v := info.Types[limit].Value; v != nil {
tVar := info.TypeOf(init.Rhs[0])
file := curFile.Node().(*ast.File)
// TODO(mkalil): use a types.Qualifier that respects the existing
// imports of this file that are visible (not shadowed) at the current position.
qual := typesinternal.FileQualifier(file, pass.Pkg)
beforeLimit, afterLimit = fmt.Sprintf("%s(", types.TypeString(tVar, qual)), ")"
info2 := &types.Info{Types: make(map[ast.Expr]types.TypeAndValue)}
if types.CheckExpr(pass.Fset, pass.Pkg, limit.Pos(), limit, info2) == nil {
tLimit := info2.TypeOf(limit)
// Eliminate conversion when safe.
//
// Redundant conversions are not only unsightly but may in some cases cause
// architecture-specific types (e.g. syscall.Timespec.Nsec) to be inserted
// into otherwise portable files.
//
// The operand must have an integer type (not, say, '1e6')
// even when assigning to an existing integer variable.
if isInteger(tLimit) {
// When declaring a new var from an untyped limit,
// the limit's default type is what matters.
if init.Tok != token.ASSIGN {
tLimit = types.Default(tLimit)
}
if types.AssignableTo(tLimit, tVar) {
beforeLimit, afterLimit = "", ""
}
}
}
}
pass.Report(analysis.Diagnostic{
Pos: init.Pos(),
End: loop.Post.End(),
Message: "for loop can be modernized using range over int",
SuggestedFixes: []analysis.SuggestedFix{{
Message: fmt.Sprintf("Replace for loop with range %s",
astutil.Format(pass.Fset, limit)),
TextEdits: append(edits, []analysis.TextEdit{
// for i := 0; i < limit; i++ {}
// ----- ---
// -------
// for i := range limit {}
// Delete init.
{
Pos: init.Rhs[0].Pos(),
End: limit.Pos(),
NewText: []byte("range "),
},
// Add "int(" before limit, if needed.
{
Pos: limit.Pos(),
End: limit.Pos(),
NewText: []byte(beforeLimit),
},
// Delete inc.
{
Pos: limit.End(),
End: loop.Post.End(),
},
// Add ")" after limit, if needed.
{
Pos: limit.End(),
End: limit.End(),
NewText: []byte(afterLimit),
},
}...),
}},
})
}
}
}
}
}
return nil, nil
}
// isScalarLvalue reports whether the specified identifier is
// address-taken or appears on the left side of an assignment.
//
// This function is valid only for scalars (x = ...),
// not for aggregates (x.a[i] = ...)
func isScalarLvalue(info *types.Info, curId inspector.Cursor) bool {
// Unfortunately we can't simply use info.Types[e].Assignable()
// as it is always true for a variable even when that variable is
// used only as an r-value. So we must inspect enclosing syntax.
cur := curId
// Strip enclosing parens.
ek := cur.ParentEdgeKind()
for ek == edge.ParenExpr_X {
cur = cur.Parent()
ek = cur.ParentEdgeKind()
}
switch ek {
case edge.AssignStmt_Lhs:
assign := cur.Parent().Node().(*ast.AssignStmt)
if assign.Tok != token.DEFINE {
return true // i = j or i += j
}
id := curId.Node().(*ast.Ident)
if v, ok := info.Defs[id]; ok && v.Pos() != id.Pos() {
return true // reassignment of i (i, j := 1, 2)
}
case edge.RangeStmt_Key:
rng := cur.Parent().Node().(*ast.RangeStmt)
if rng.Tok == token.ASSIGN {
return true // "for k, v = range x" is like an AssignStmt to k, v
}
case edge.IncDecStmt_X:
return true // i++, i--
case edge.UnaryExpr_X:
if cur.Parent().Node().(*ast.UnaryExpr).Op == token.AND {
return true // &i
}
}
return false
}
// enclosingSignature returns the signature of the innermost
// function enclosing the syntax node denoted by cur
// or nil if the node is not within a function.
//
// TODO(adonovan): factor with gopls/internal/util/typesutil.EnclosingSignature.
func enclosingSignature(cur inspector.Cursor, info *types.Info) *types.Signature {
if c, ok := enclosingFunc(cur); ok {
switch n := c.Node().(type) {
case *ast.FuncDecl:
if f, ok := info.Defs[n.Name]; ok {
return f.Type().(*types.Signature)
}
case *ast.FuncLit:
if f, ok := info.Types[n]; ok {
return f.Type.(*types.Signature)
}
}
}
return nil
}
================================================
FILE: go/analysis/passes/modernize/reflect.go
================================================
// Copyright 2025 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package modernize
// This file defines modernizers that use the "reflect" package.
import (
"go/ast"
"go/types"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/ast/edge"
"golang.org/x/tools/go/types/typeutil"
"golang.org/x/tools/internal/analysis/analyzerutil"
typeindexanalyzer "golang.org/x/tools/internal/analysis/typeindex"
"golang.org/x/tools/internal/astutil"
"golang.org/x/tools/internal/refactor"
"golang.org/x/tools/internal/typesinternal"
"golang.org/x/tools/internal/typesinternal/typeindex"
"golang.org/x/tools/internal/versions"
)
var ReflectTypeForAnalyzer = &analysis.Analyzer{
Name: "reflecttypefor",
Doc: analyzerutil.MustExtractDoc(doc, "reflecttypefor"),
Requires: []*analysis.Analyzer{
inspect.Analyzer,
typeindexanalyzer.Analyzer,
},
Run: reflecttypefor,
URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/modernize#reflecttypefor",
}
func reflecttypefor(pass *analysis.Pass) (any, error) {
var (
index = pass.ResultOf[typeindexanalyzer.Analyzer].(*typeindex.Index)
info = pass.TypesInfo
reflectTypeOf = index.Object("reflect", "TypeOf")
)
for curCall := range index.Calls(reflectTypeOf) {
call := curCall.Node().(*ast.CallExpr)
// Have: reflect.TypeOf(expr)
expr := call.Args[0]
// reflect.TypeFor cannot be instantiated with an untyped nil.
// We use type information rather than checking the identifier name
// to correctly handle edge cases where "nil" is shadowed (e.g. nil := "nil").
if info.Types[expr].IsNil() {
continue
}
if !typesinternal.NoEffects(info, expr) {
continue // don't eliminate operand: may have effects
}
t := info.TypeOf(expr)
var edits []analysis.TextEdit
// Special cases for TypeOf((*T)(nil)).Elem(), and
// TypeOf([]T(nil)).Elem(), needed when T is an interface type.
if curCall.ParentEdgeKind() == edge.SelectorExpr_X {
curSel := unparenEnclosing(curCall).Parent()
if curSel.ParentEdgeKind() == edge.CallExpr_Fun {
call2 := unparenEnclosing(curSel).Parent().Node().(*ast.CallExpr) // potentially .Elem()
obj := typeutil.Callee(info, call2)
if typesinternal.IsMethodNamed(obj, "reflect", "Type", "Elem") {
// reflect.TypeOf(expr).Elem()
// -------
// reflect.TypeOf(expr)
if typ, hasElem := t.(interface{ Elem() types.Type }); hasElem {
// Have: TypeOf(expr).Elem() where expr is *T, []T, [k]T, chan T, map[K]T, etc.
t = typ.Elem()
edits = []analysis.TextEdit{{
Pos: call.End(),
End: call2.End(),
}}
}
}
}
}
// TypeOf(x) where x has an interface type is a
// dynamic operation; don't transform it to TypeFor.
// (edits == nil means "not the Elem() special case".)
if types.IsInterface(t) && edits == nil {
continue
}
file := astutil.EnclosingFile(curCall)
if !analyzerutil.FileUsesGoVersion(pass, file, versions.Go1_22) {
continue // TypeFor requires go1.22
}
tokFile := pass.Fset.File(file.Pos())
// Format the type as valid Go syntax.
// TODO(adonovan): FileQualifier needs to respect
// visibility at the current point, and either fail
// or edit the imports as needed.
qual := typesinternal.FileQualifier(file, pass.Pkg)
tstr := types.TypeString(t, qual)
sel, ok := call.Fun.(*ast.SelectorExpr)
if !ok {
continue // e.g. reflect was dot-imported
}
// Don't offer a fix if the type contains an unnamed struct or unnamed
// interface because the replacement would be significantly more verbose.
// (See golang/go#76698)
if isComplicatedType(t) {
continue
}
// Don't offer the fix if the type string is too long. We define "too
// long" as more than three times the length of the original expression
// and at least 16 characters (a 3x length increase of a very
// short expression should not be cause for skipping the fix).
oldLen := int(expr.End() - expr.Pos())
newLen := len(tstr)
if newLen >= 16 && newLen > 3*oldLen {
continue
}
// If the call argument contains the last use
// of a variable, as in:
// var zero T
// reflect.TypeOf(zero)
// remove the declaration of that variable.
curArg0 := curCall.ChildAt(edge.CallExpr_Args, 0)
edits = append(edits, refactor.DeleteUnusedVars(index, info, tokFile, curArg0)...)
pass.Report(analysis.Diagnostic{
Pos: call.Fun.Pos(),
End: call.Fun.End(),
Message: "reflect.TypeOf call can be simplified using TypeFor",
SuggestedFixes: []analysis.SuggestedFix{{
// reflect.TypeOf (...T value...)
// ------ -------------
// reflect.TypeFor[T]( )
Message: "Replace TypeOf by TypeFor",
TextEdits: append([]analysis.TextEdit{
{
Pos: sel.Sel.Pos(),
End: sel.Sel.End(),
NewText: []byte("TypeFor[" + tstr + "]"),
},
// delete (pure) argument
{
Pos: call.Lparen + 1,
End: call.Rparen,
},
}, edits...),
}},
})
}
return nil, nil
}
// isComplicatedType reports whether type t is complicated, e.g. it is or contains an
// unnamed struct, interface, or function signature.
func isComplicatedType(t types.Type) bool {
var check func(typ types.Type) bool
check = func(typ types.Type) bool {
switch t := typ.(type) {
case typesinternal.NamedOrAlias:
for ta := range t.TypeArgs().Types() {
if check(ta) {
return true
}
}
return false
case *types.Struct, *types.Interface, *types.Signature:
// These are complex types with potentially many elements
// so we should avoid duplicating their definition.
return true
case *types.Pointer:
return check(t.Elem())
case *types.Slice:
return check(t.Elem())
case *types.Array:
return check(t.Elem())
case *types.Chan:
return check(t.Elem())
case *types.Map:
return check(t.Key()) || check(t.Elem())
case *types.Basic:
return false
case *types.TypeParam:
return false
default:
// Includes types.Union
return true
}
}
return check(t)
}
================================================
FILE: go/analysis/passes/modernize/slices.go
================================================
// Copyright 2024 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package modernize
import (
"fmt"
"go/ast"
"go/types"
"slices"
"strconv"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/types/typeutil"
"golang.org/x/tools/internal/analysis/analyzerutil"
"golang.org/x/tools/internal/astutil"
"golang.org/x/tools/internal/refactor"
"golang.org/x/tools/internal/typesinternal"
"golang.org/x/tools/internal/versions"
)
// Warning: this analyzer is not safe to enable by default.
var AppendClippedAnalyzer = &analysis.Analyzer{
Name: "appendclipped",
Doc: analyzerutil.MustExtractDoc(doc, "appendclipped"),
Requires: []*analysis.Analyzer{inspect.Analyzer},
Run: appendclipped,
URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/modernize#appendclipped",
}
// The appendclipped pass offers to simplify a tower of append calls:
//
// append(append(append(base, a...), b..., c...)
//
// with a call to go1.21's slices.Concat(base, a, b, c), or simpler
// replacements such as slices.Clone(a) in degenerate cases.
//
// We offer bytes.Clone in preference to slices.Clone where
// appropriate, if the package already imports "bytes";
// their behaviors are identical.
//
// The base expression must denote a clipped slice (see [isClipped]
// for definition), otherwise the replacement might eliminate intended
// side effects to the base slice's array.
//
// Examples:
//
// append(append(append(x[:0:0], a...), b...), c...) -> slices.Concat(a, b, c)
// append(append(slices.Clip(a), b...) -> slices.Concat(a, b)
// append([]T{}, a...) -> slices.Clone(a)
// append([]string(nil), os.Environ()...) -> os.Environ()
//
// The fix does not always preserve nilness the of base slice when the
// addends (a, b, c) are all empty (see #73557).
func appendclipped(pass *analysis.Pass) (any, error) {
// Skip the analyzer in packages where its
// fixes would create an import cycle.
if within(pass, "slices", "bytes", "runtime") {
return nil, nil
}
info := pass.TypesInfo
// sliceArgs is a non-empty (reversed) list of slices to be concatenated.
simplifyAppendEllipsis := func(file *ast.File, call *ast.CallExpr, base ast.Expr, sliceArgs []ast.Expr) {
// Only appends whose base is a clipped slice can be simplified:
// We must conservatively assume an append to an unclipped slice
// such as append(y[:0], x...) is intended to have effects on y.
clipped, empty := clippedSlice(info, base)
if clipped == nil {
return
}
// If any slice arg has a different type from the base
// (and thus the result) don't offer a fix, to avoid
// changing the return type, e.g:
//
// type S []int
// - x := append([]int(nil), S{}...) // x : []int
// + x := slices.Clone(S{}) // x : S
//
// We could do better by inserting an explicit generic
// instantiation:
//
// x := slices.Clone[[]int](S{})
//
// but this is often unnecessary and unwanted, such as
// when the value is used an in assignment context that
// provides an explicit type:
//
// var x []int = slices.Clone(S{})
baseType := info.TypeOf(base)
for _, arg := range sliceArgs {
if !types.Identical(info.TypeOf(arg), baseType) {
return
}
}
// If the (clipped) base is empty, it may be safely ignored.
// Otherwise treat it (or its unclipped subexpression, if possible)
// as just another arg (the first) to Concat.
//
// TODO(adonovan): not so fast! If all the operands
// are empty, then the nilness of base matters, because
// append preserves nilness whereas Concat does not (#73557).
if !empty {
sliceArgs = append(sliceArgs, clipped)
}
slices.Reverse(sliceArgs)
// TODO(adonovan): simplify sliceArgs[0] further: slices.Clone(s) -> s
// Concat of a single (non-trivial) slice degenerates to Clone.
if len(sliceArgs) == 1 {
s := sliceArgs[0]
// Special case for common but redundant clone of os.Environ().
// append(zerocap, os.Environ()...) -> os.Environ()
if scall, ok := s.(*ast.CallExpr); ok {
obj := typeutil.Callee(info, scall)
if typesinternal.IsFunctionNamed(obj, "os", "Environ") {
pass.Report(analysis.Diagnostic{
Pos: call.Pos(),
End: call.End(),
Message: "Redundant clone of os.Environ()",
SuggestedFixes: []analysis.SuggestedFix{{
Message: "Eliminate redundant clone",
TextEdits: []analysis.TextEdit{{
Pos: call.Pos(),
End: call.End(),
NewText: []byte(astutil.Format(pass.Fset, s)),
}},
}},
})
return
}
}
// If the slice type is []byte, and the file imports
// "bytes" but not "slices", prefer the (behaviorally
// identical) bytes.Clone for local consistency.
// https://go.dev/issue/70815#issuecomment-2671572984
fileImports := func(path string) bool {
return slices.ContainsFunc(file.Imports, func(spec *ast.ImportSpec) bool {
return first(strconv.Unquote(spec.Path.Value)) == path
})
}
clonepkg := cond(
types.Identical(info.TypeOf(call), byteSliceType) &&
!fileImports("slices") && fileImports("bytes"),
"bytes",
"slices")
// append(zerocap, s...) -> slices.Clone(s) or bytes.Clone(s)
//
// This is unsound if s is empty and its nilness
// differs from zerocap (#73557).
prefix, importEdits := refactor.AddImport(info, file, clonepkg, clonepkg, "Clone", call.Pos())
message := fmt.Sprintf("Replace append with %s.Clone", clonepkg)
pass.Report(analysis.Diagnostic{
Pos: call.Pos(),
End: call.End(),
Message: message,
SuggestedFixes: []analysis.SuggestedFix{{
Message: message,
TextEdits: append(importEdits, []analysis.TextEdit{{
Pos: call.Pos(),
End: call.End(),
NewText: fmt.Appendf(nil, "%sClone(%s)", prefix, astutil.Format(pass.Fset, s)),
}}...),
}},
})
return
}
// append(append(append(base, a...), b..., c...) -> slices.Concat(base, a, b, c)
//
// This is unsound if all slices are empty and base is non-nil (#73557).
prefix, importEdits := refactor.AddImport(info, file, "slices", "slices", "Concat", call.Pos())
pass.Report(analysis.Diagnostic{
Pos: call.Pos(),
End: call.End(),
Message: "Replace append with slices.Concat",
SuggestedFixes: []analysis.SuggestedFix{{
Message: "Replace append with slices.Concat",
TextEdits: append(importEdits, []analysis.TextEdit{{
Pos: call.Pos(),
End: call.End(),
NewText: fmt.Appendf(nil, "%sConcat(%s)", prefix, formatExprs(pass.Fset, sliceArgs)),
}}...),
}},
})
}
// Mark nested calls to append so that we don't emit diagnostics for them.
skip := make(map[*ast.CallExpr]bool)
// Visit calls of form append(x, y...).
for curFile := range filesUsingGoVersion(pass, versions.Go1_21) {
file := curFile.Node().(*ast.File)
for curCall := range curFile.Preorder((*ast.CallExpr)(nil)) {
call := curCall.Node().(*ast.CallExpr)
if skip[call] {
continue
}
// Recursively unwrap ellipsis calls to append, so
// append(append(append(base, a...), b..., c...)
// yields (base, [c b a]).
base, slices := ast.Expr(call), []ast.Expr(nil) // base case: (call, nil)
again:
if call, ok := base.(*ast.CallExpr); ok {
if id, ok := call.Fun.(*ast.Ident); ok &&
call.Ellipsis.IsValid() &&
len(call.Args) == 2 &&
info.Uses[id] == builtinAppend {
// Have: append(base, s...)
base, slices = call.Args[0], append(slices, call.Args[1])
skip[call] = true
goto again
}
}
if len(slices) > 0 {
simplifyAppendEllipsis(file, call, base, slices)
}
}
}
return nil, nil
}
// clippedSlice returns res != nil if e denotes a slice that is
// definitely clipped, that is, its len(s)==cap(s).
//
// The value of res is either the same as e or is a subexpression of e
// that denotes the same slice but without the clipping operation.
//
// In addition, it reports whether the slice is definitely empty.
//
// Examples of clipped slices:
//
// x[:0:0] (empty)
// []T(nil) (empty)
// Slice{} (empty)
// x[:len(x):len(x)] (nonempty) res=x
// x[:k:k] (nonempty)
// slices.Clip(x) (nonempty) res=x
//
// TODO(adonovan): Add a check that the expression x has no side effects in
// case x[:len(x):len(x)] -> x. Now the program behavior may change.
func clippedSlice(info *types.Info, e ast.Expr) (res ast.Expr, empty bool) {
switch e := e.(type) {
case *ast.SliceExpr:
// x[:0:0], x[:len(x):len(x)], x[:k:k]
if e.Slice3 && e.High != nil && e.Max != nil && astutil.EqualSyntax(e.High, e.Max) { // x[:k:k]
res = e
empty = isZeroIntConst(info, e.High) // x[:0:0]
if call, ok := e.High.(*ast.CallExpr); ok &&
typeutil.Callee(info, call) == builtinLen &&
astutil.EqualSyntax(call.Args[0], e.X) {
res = e.X // x[:len(x):len(x)] -> x
}
return
}
return
case *ast.CallExpr:
// []T(nil)?
if info.Types[e.Fun].IsType() &&
is[*ast.Ident](e.Args[0]) &&
info.Uses[e.Args[0].(*ast.Ident)] == builtinNil {
return e, true
}
// slices.Clip(x)?
obj := typeutil.Callee(info, e)
if typesinternal.IsFunctionNamed(obj, "slices", "Clip") {
return e.Args[0], false // slices.Clip(x) -> x
}
case *ast.CompositeLit:
// Slice{}?
if len(e.Elts) == 0 {
return e, true
}
}
return nil, false
}
================================================
FILE: go/analysis/passes/modernize/slicescontains.go
================================================
// Copyright 2024 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package modernize
import (
"fmt"
"go/ast"
"go/token"
"go/types"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/ast/inspector"
"golang.org/x/tools/go/types/typeutil"
"golang.org/x/tools/internal/analysis/analyzerutil"
typeindexanalyzer "golang.org/x/tools/internal/analysis/typeindex"
"golang.org/x/tools/internal/astutil"
"golang.org/x/tools/internal/refactor"
"golang.org/x/tools/internal/typeparams"
"golang.org/x/tools/internal/typesinternal/typeindex"
"golang.org/x/tools/internal/versions"
)
var SlicesContainsAnalyzer = &analysis.Analyzer{
Name: "slicescontains",
Doc: analyzerutil.MustExtractDoc(doc, "slicescontains"),
Requires: []*analysis.Analyzer{
inspect.Analyzer,
typeindexanalyzer.Analyzer,
},
Run: slicescontains,
URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/modernize#slicescontains",
}
// The slicescontains pass identifies loops that can be replaced by a
// call to slices.Contains{,Func}. For example:
//
// for i, elem := range s {
// if elem == needle {
// ...
// break
// }
// }
//
// =>
//
// if slices.Contains(s, needle) { ... }
//
// Variants:
// - if the if-condition is f(elem), the replacement
// uses slices.ContainsFunc(s, f).
// - if the if-body is "return true" and the fallthrough
// statement is "return false" (or vice versa), the
// loop becomes "return [!]slices.Contains(...)".
// - if the if-body is "found = true" and the previous
// statement is "found = false" (or vice versa), the
// loop becomes "found = [!]slices.Contains(...)".
//
// It may change cardinality of effects of the "needle" expression.
// (Mostly this appears to be a desirable optimization, avoiding
// redundantly repeated evaluation.)
//
// TODO(adonovan): Add a check that needle/predicate expression from
// if-statement has no effects. Now the program behavior may change.
func slicescontains(pass *analysis.Pass) (any, error) {
// Skip the analyzer in packages where its
// fixes would create an import cycle.
if within(pass, "slices", "runtime") {
return nil, nil
}
var (
index = pass.ResultOf[typeindexanalyzer.Analyzer].(*typeindex.Index)
info = pass.TypesInfo
)
// check is called for each RangeStmt of this form:
// for i, elem := range s { if cond { ... } }
check := func(file *ast.File, curRange inspector.Cursor) {
rng := curRange.Node().(*ast.RangeStmt)
ifStmt := rng.Body.List[0].(*ast.IfStmt)
// isSliceElem reports whether e denotes the
// current slice element (elem or s[i]).
isSliceElem := func(e ast.Expr) bool {
if rng.Value != nil && astutil.EqualSyntax(e, rng.Value) {
return true // "elem"
}
if x, ok := e.(*ast.IndexExpr); ok &&
astutil.EqualSyntax(x.X, rng.X) &&
astutil.EqualSyntax(x.Index, rng.Key) {
return true // "s[i]"
}
return false
}
// Examine the condition for one of these forms:
//
// - if elem or s[i] == needle { ... } => Contains
// - if predicate(s[i] or elem) { ... } => ContainsFunc
var (
funcName string // "Contains" or "ContainsFunc"
arg2 ast.Expr // second argument to func (needle or predicate)
)
switch cond := ifStmt.Cond.(type) {
case *ast.BinaryExpr:
if cond.Op == token.EQL {
var elem ast.Expr
if isSliceElem(cond.X) {
funcName = "Contains"
elem = cond.X
arg2 = cond.Y // "if elem == needle"
} else if isSliceElem(cond.Y) {
funcName = "Contains"
elem = cond.Y
arg2 = cond.X // "if needle == elem"
}
// Reject if elem and needle have different types.
if elem != nil {
tElem := info.TypeOf(elem)
tNeedle := info.TypeOf(arg2)
if !types.Identical(tElem, tNeedle) {
// Avoid ill-typed slices.Contains([]error, any).
if !types.AssignableTo(tNeedle, tElem) {
return
}
// TODO(adonovan): relax this check to allow
// slices.Contains([]error, error(any)),
// inserting an explicit widening conversion
// around the needle.
return
}
}
}
case *ast.CallExpr:
if len(cond.Args) == 1 &&
isSliceElem(cond.Args[0]) &&
typeutil.Callee(info, cond) != nil { // not a conversion
// Attempt to get signature
sig, isSignature := info.TypeOf(cond.Fun).(*types.Signature)
if isSignature {
// skip variadic functions
if sig.Variadic() {
return
}
// Slice element type must match function parameter type.
var (
tElem = typeparams.CoreType(info.TypeOf(rng.X)).(*types.Slice).Elem()
tParam = sig.Params().At(0).Type()
)
if !types.Identical(tElem, tParam) {
return
}
}
funcName = "ContainsFunc"
arg2 = cond.Fun // "if predicate(elem)"
}
}
if funcName == "" {
return // not a candidate for Contains{,Func}
}
// body is the "true" body.
body := ifStmt.Body
if len(body.List) == 0 {
// (We could perhaps delete the loop entirely.)
return
}
// Reject if the body, needle or predicate references either range variable.
usesRangeVar := func(n ast.Node) bool {
cur, ok := curRange.FindNode(n)
if !ok {
panic(fmt.Sprintf("FindNode(%T) failed", n))
}
return uses(index, cur, info.Defs[rng.Key.(*ast.Ident)]) ||
rng.Value != nil && uses(index, cur, info.Defs[rng.Value.(*ast.Ident)])
}
if usesRangeVar(body) {
// Body uses range var "i" or "elem".
//
// (The check for "i" could be relaxed when we
// generalize this to support slices.Index;
// and the check for "elem" could be relaxed
// if "elem" can safely be replaced in the
// body by "needle".)
return
}
if usesRangeVar(arg2) {
return
}
// Prepare slices.Contains{,Func} call.
prefix, importEdits := refactor.AddImport(info, file, "slices", "slices", funcName, rng.Pos())
contains := fmt.Sprintf("%s%s(%s, %s)",
prefix,
funcName,
astutil.Format(pass.Fset, rng.X),
astutil.Format(pass.Fset, arg2))
report := func(edits []analysis.TextEdit) {
pass.Report(analysis.Diagnostic{
Pos: rng.Pos(),
End: rng.End(),
Message: fmt.Sprintf("Loop can be simplified using slices.%s", funcName),
SuggestedFixes: []analysis.SuggestedFix{{
Message: "Replace loop by call to slices." + funcName,
TextEdits: append(edits, importEdits...),
}},
})
}
// Last statement of body must return/break out of the loop.
//
// TODO(adonovan): opt:consider avoiding FindNode with new API of form:
// curRange.Get(edge.RangeStmt_Body, -1).
// Get(edge.BodyStmt_List, 0).
// Get(edge.IfStmt_Body)
curBody, _ := curRange.FindNode(body)
curLastStmt, _ := curBody.LastChild()
// Reject if any statement in the body except the
// last has a free continuation (continue or break)
// that might affected by melting down the loop.
//
// TODO(adonovan): relax check by analyzing branch target.
numBodyStmts := 0
for curBodyStmt := range curBody.Children() {
numBodyStmts += 1
if curBodyStmt != curLastStmt {
for range curBodyStmt.Preorder((*ast.BranchStmt)(nil), (*ast.ReturnStmt)(nil)) {
return
}
}
}
switch lastStmt := curLastStmt.Node().(type) {
case *ast.ReturnStmt:
// Have: for ... range seq { if ... { stmts; return x } }
// Special case:
// body={ return true } next="return false" (or negation)
// => return [!]slices.Contains(...)
if curNext, ok := curRange.NextSibling(); ok {
nextStmt := curNext.Node().(ast.Stmt)
tval := isReturnTrueOrFalse(info, lastStmt)
fval := isReturnTrueOrFalse(info, nextStmt)
if len(body.List) == 1 && tval*fval < 0 {
// for ... { if ... { return true/false } }
// => return [!]slices.Contains(...)
report([]analysis.TextEdit{
// Delete the range statement and following space.
{
Pos: rng.Pos(),
End: nextStmt.Pos(),
},
// Change return to [!]slices.Contains(...).
{
Pos: nextStmt.Pos(),
End: nextStmt.End(),
NewText: fmt.Appendf(nil, "return %s%s",
cond(tval > 0, "", "!"),
contains),
},
})
return
}
}
// General case:
// => if slices.Contains(...) { stmts; return x }
report([]analysis.TextEdit{
// Replace "for ... { if ... " with "if slices.Contains(...)".
{
Pos: rng.Pos(),
End: ifStmt.Body.Pos(),
NewText: fmt.Appendf(nil, "if %s ", contains),
},
// Delete '}' of range statement and preceding space.
{
Pos: ifStmt.Body.End(),
End: rng.End(),
},
})
return
case *ast.BranchStmt:
if lastStmt.Tok == token.BREAK && lastStmt.Label == nil { // unlabeled break
// Have: for ... { if ... { stmts; break } }
if numBodyStmts == 1 {
// If the only stmt in the body is an unlabeled "break" that
// will get deleted in the fix, don't suggest a fix, as it
// produces confusing code:
// if slices.Contains(slice, f) {}
// Explicitly discarding the result isn't much better:
// _ = slices.Contains(slice, f) // just for effects
// See https://go.dev/issue/77677.
return
}
var prevStmt ast.Stmt // previous statement to range (if any)
if curPrev, ok := curRange.PrevSibling(); ok {
// If the RangeStmt's previous sibling is a Stmt,
// the RangeStmt must be among the Body list of
// a BlockStmt, CauseClause, or CommClause.
// In all cases, the prevStmt is the immediate
// predecessor of the RangeStmt during execution.
//
// (This is not true for Stmts in general;
// see [Cursor.Children] and #71074.)
prevStmt, _ = curPrev.Node().(ast.Stmt)
}
// Special case:
// prev="lhs = false" body={ lhs = true; break }
// => lhs = slices.Contains(...) (or its negation)
if assign, ok := body.List[0].(*ast.AssignStmt); ok &&
len(body.List) == 2 &&
assign.Tok == token.ASSIGN &&
len(assign.Lhs) == 1 &&
len(assign.Rhs) == 1 {
// Have: body={ lhs = rhs; break }
assignBool := isTrueOrFalse(info, assign.Rhs[0])
if prevAssign, ok := prevStmt.(*ast.AssignStmt); ok &&
len(prevAssign.Lhs) == 1 &&
len(prevAssign.Rhs) == 1 &&
assignBool != 0 && // non-bool assignments don't apply in this case
astutil.EqualSyntax(prevAssign.Lhs[0], assign.Lhs[0]) &&
assignBool == -isTrueOrFalse(info, prevAssign.Rhs[0]) {
// Have:
// lhs = false
// for ... { if ... { lhs = true; break } }
// =>
// lhs = slices.Contains(...)
//
// TODO(adonovan):
// - support "var lhs bool = false" and variants.
// - allow the break to be omitted.
neg := cond(assignBool < 0, "!", "")
report([]analysis.TextEdit{
// Replace "rhs" of previous assignment by [!]slices.Contains(...)
{
Pos: prevAssign.Rhs[0].Pos(),
End: prevAssign.Rhs[0].End(),
NewText: []byte(neg + contains),
},
// Delete the loop and preceding space.
{
Pos: prevAssign.Rhs[0].End(),
End: rng.End(),
},
})
return
}
}
// General case:
// for ... { if ... { stmts; break } }
// => if slices.Contains(...) { stmts }
report([]analysis.TextEdit{
// Replace "for ... { if ... " with "if slices.Contains(...)".
{
Pos: rng.Pos(),
End: ifStmt.Body.Pos(),
NewText: fmt.Appendf(nil, "if %s ", contains),
},
// Delete break statement and preceding space.
{
Pos: func() token.Pos {
if len(body.List) > 1 {
beforeBreak, _ := curLastStmt.PrevSibling()
return beforeBreak.Node().End()
}
return lastStmt.Pos()
}(),
End: lastStmt.End(),
},
// Delete '}' of range statement and preceding space.
{
Pos: ifStmt.Body.End(),
End: rng.End(),
},
})
return
}
}
}
for curFile := range filesUsingGoVersion(pass, versions.Go1_21) {
file := curFile.Node().(*ast.File)
for curRange := range curFile.Preorder((*ast.RangeStmt)(nil)) {
rng := curRange.Node().(*ast.RangeStmt)
if is[*ast.Ident](rng.Key) &&
rng.Tok == token.DEFINE &&
len(rng.Body.List) == 1 &&
is[*types.Slice](typeparams.CoreType(info.TypeOf(rng.X))) {
// Have:
// - for _, elem := range s { S }
// - for i := range s { S }
if ifStmt, ok := rng.Body.List[0].(*ast.IfStmt); ok &&
ifStmt.Init == nil && ifStmt.Else == nil {
// Have: for i, elem := range s { if cond { ... } }
check(file, curRange)
}
}
}
}
return nil, nil
}
// -- helpers --
// isReturnTrueOrFalse returns nonzero if stmt returns true (+1) or false (-1).
func isReturnTrueOrFalse(info *types.Info, stmt ast.Stmt) int {
if ret, ok := stmt.(*ast.ReturnStmt); ok && len(ret.Results) == 1 {
return isTrueOrFalse(info, ret.Results[0])
}
return 0
}
// isTrueOrFalse returns nonzero if expr is literally true (+1) or false (-1).
func isTrueOrFalse(info *types.Info, expr ast.Expr) int {
if id, ok := expr.(*ast.Ident); ok {
switch info.Uses[id] {
case builtinTrue:
return +1
case builtinFalse:
return -1
}
}
return 0
}
================================================
FILE: go/analysis/passes/modernize/slicesdelete.go
================================================
// Copyright 2024 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package modernize
import (
"go/ast"
"go/constant"
"go/token"
"go/types"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/internal/analysis/analyzerutil"
"golang.org/x/tools/internal/astutil"
"golang.org/x/tools/internal/refactor"
"golang.org/x/tools/internal/typesinternal"
"golang.org/x/tools/internal/versions"
)
// Warning: this analyzer is not safe to enable by default (not nil-preserving).
var SlicesDeleteAnalyzer = &analysis.Analyzer{
Name: "slicesdelete",
Doc: analyzerutil.MustExtractDoc(doc, "slicesdelete"),
Requires: []*analysis.Analyzer{inspect.Analyzer},
Run: slicesdelete,
URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/modernize#slicesdelete",
}
// The slicesdelete pass attempts to replace instances of append(s[:i], s[i+k:]...)
// with slices.Delete(s, i, i+k) where k is some positive constant.
// Other variations that will also have suggested replacements include:
// append(s[:i-1], s[i:]...) and append(s[:i+k1], s[i+k2:]) where k2 > k1.
func slicesdelete(pass *analysis.Pass) (any, error) {
// Skip the analyzer in packages where its
// fixes would create an import cycle.
if within(pass, "slices", "runtime") {
return nil, nil
}
info := pass.TypesInfo
report := func(file *ast.File, call *ast.CallExpr, slice1, slice2 *ast.SliceExpr) {
insert := func(pos token.Pos, text string) analysis.TextEdit {
return analysis.TextEdit{Pos: pos, End: pos, NewText: []byte(text)}
}
isIntExpr := func(e ast.Expr) bool {
return types.Identical(types.Default(info.TypeOf(e)), builtinInt.Type())
}
isIntShadowed := func() bool {
scope := info.Scopes[file].Innermost(call.Lparen)
if _, obj := scope.LookupParent("int", call.Lparen); obj != builtinInt {
return true // int type is shadowed
}
return false
}
prefix, edits := refactor.AddImport(info, file, "slices", "slices", "Delete", call.Pos())
// append's indices may be any integer type; slices.Delete requires int.
// Insert int conversions as needed (and if possible).
if isIntShadowed() && (!isIntExpr(slice1.High) || !isIntExpr(slice2.Low)) {
return
}
if !isIntExpr(slice1.High) {
edits = append(edits,
insert(slice1.High.Pos(), "int("),
insert(slice1.High.End(), ")"),
)
}
if !isIntExpr(slice2.Low) {
edits = append(edits,
insert(slice2.Low.Pos(), "int("),
insert(slice2.Low.End(), ")"),
)
}
pass.Report(analysis.Diagnostic{
Pos: call.Pos(),
End: call.End(),
Message: "Replace append with slices.Delete",
SuggestedFixes: []analysis.SuggestedFix{{
Message: "Replace append with slices.Delete",
TextEdits: append(edits, []analysis.TextEdit{
// Change name of called function.
{
Pos: call.Fun.Pos(),
End: call.Fun.End(),
NewText: []byte(prefix + "Delete"),
},
// Delete ellipsis.
{
Pos: call.Ellipsis,
End: call.Ellipsis + token.Pos(len("...")), // delete ellipsis
},
// Remove second slice variable name.
{
Pos: slice2.X.Pos(),
End: slice2.X.End(),
},
// Insert after first slice variable name.
{
Pos: slice1.X.End(),
NewText: []byte(", "),
},
// Remove brackets and colons.
{
Pos: slice1.Lbrack,
End: slice1.High.Pos(),
},
{
Pos: slice1.Rbrack,
End: slice1.Rbrack + 1,
},
{
Pos: slice2.Lbrack,
End: slice2.Lbrack + 1,
},
{
Pos: slice2.Low.End(),
End: slice2.Rbrack + 1,
},
}...),
}},
})
}
for curFile := range filesUsingGoVersion(pass, versions.Go1_21) {
file := curFile.Node().(*ast.File)
for curCall := range curFile.Preorder((*ast.CallExpr)(nil)) {
call := curCall.Node().(*ast.CallExpr)
if id, ok := call.Fun.(*ast.Ident); ok && len(call.Args) == 2 {
// Verify we have append with two slices and ... operator,
// the first slice has no low index and second slice has no
// high index, and not a three-index slice.
if call.Ellipsis.IsValid() && info.Uses[id] == builtinAppend {
slice1, ok1 := call.Args[0].(*ast.SliceExpr)
slice2, ok2 := call.Args[1].(*ast.SliceExpr)
if ok1 && slice1.Low == nil && !slice1.Slice3 &&
ok2 && slice2.High == nil && !slice2.Slice3 &&
astutil.EqualSyntax(slice1.X, slice2.X) &&
typesinternal.NoEffects(info, slice1.X) &&
increasingSliceIndices(info, slice1.High, slice2.Low) {
// Have append(s[:a], s[b:]...) where we can verify a < b.
report(file, call, slice1, slice2)
}
}
}
}
}
return nil, nil
}
// Given two slice indices a and b, returns true if we can verify that a < b.
// It recognizes certain forms such as i+k1 < i+k2 where k1 < k2.
func increasingSliceIndices(info *types.Info, a, b ast.Expr) bool {
// Given an expression of the form i±k, returns (i, k)
// where k is a signed constant. Otherwise it returns (e, 0).
split := func(e ast.Expr) (ast.Expr, constant.Value) {
if binary, ok := e.(*ast.BinaryExpr); ok && (binary.Op == token.SUB || binary.Op == token.ADD) {
// Negate constants if operation is subtract instead of add
if k := info.Types[binary.Y].Value; k != nil {
return binary.X, constant.UnaryOp(binary.Op, k, 0) // i ± k
}
}
return e, constant.MakeInt64(0)
}
// Handle case where either a or b is a constant
ak := info.Types[a].Value
bk := info.Types[b].Value
if ak != nil || bk != nil {
return ak != nil && bk != nil && constant.Compare(ak, token.LSS, bk)
}
ai, ak := split(a)
bi, bk := split(b)
return astutil.EqualSyntax(ai, bi) && constant.Compare(ak, token.LSS, bk)
}
================================================
FILE: go/analysis/passes/modernize/sortslice.go
================================================
// Copyright 2024 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package modernize
import (
"go/ast"
"go/token"
"go/types"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/internal/analysis/analyzerutil"
typeindexanalyzer "golang.org/x/tools/internal/analysis/typeindex"
"golang.org/x/tools/internal/astutil"
"golang.org/x/tools/internal/refactor"
"golang.org/x/tools/internal/typesinternal/typeindex"
"golang.org/x/tools/internal/versions"
)
// (Not to be confused with go/analysis/passes/sortslice.)
var SlicesSortAnalyzer = &analysis.Analyzer{
Name: "slicessort",
Doc: analyzerutil.MustExtractDoc(doc, "slicessort"),
Requires: []*analysis.Analyzer{
inspect.Analyzer,
typeindexanalyzer.Analyzer,
},
Run: slicessort,
URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/modernize#slicessort",
}
// The slicessort pass replaces sort.Slice(slice, less) with
// slices.Sort(slice) when slice is a []T and less is a FuncLit
// equivalent to cmp.Ordered[T].
//
// sort.Slice(s, func(i, j int) bool { return s[i] < s[j] })
// => slices.Sort(s)
//
// There is no slices.SortStable.
//
// TODO(adonovan): support
//
// - sort.Slice(s, func(i, j int) bool { return s[i] ... s[j] })
// -> slices.SortFunc(s, func(x, y T) int { return x ... y })
// iff all uses of i, j can be replaced by s[i], s[j] and "<" can be replaced with cmp.Compare.
//
// - As above for sort.SliceStable -> slices.SortStableFunc.
//
// - sort.Sort(x) where x has a named slice type whose Less method is the natural order.
// -> sort.Slice(x)
func slicessort(pass *analysis.Pass) (any, error) {
// Skip the analyzer in packages where its
// fixes would create an import cycle.
if within(pass, "slices", "sort", "runtime") {
return nil, nil
}
var (
info = pass.TypesInfo
index = pass.ResultOf[typeindexanalyzer.Analyzer].(*typeindex.Index)
sortSlice = index.Object("sort", "Slice")
)
for curCall := range index.Calls(sortSlice) {
call := curCall.Node().(*ast.CallExpr)
if lit, ok := call.Args[1].(*ast.FuncLit); ok && len(lit.Body.List) == 1 {
sig := info.Types[lit.Type].Type.(*types.Signature)
// Have: sort.Slice(s, func(i, j int) bool { return ... })
s := call.Args[0]
i := sig.Params().At(0)
j := sig.Params().At(1)
if ret, ok := lit.Body.List[0].(*ast.ReturnStmt); ok {
if compare, ok := ret.Results[0].(*ast.BinaryExpr); ok && compare.Op == token.LSS {
// isIndex reports whether e is s[v].
isIndex := func(e ast.Expr, v *types.Var) bool {
index, ok := e.(*ast.IndexExpr)
return ok &&
astutil.EqualSyntax(index.X, s) &&
is[*ast.Ident](index.Index) &&
info.Uses[index.Index.(*ast.Ident)] == v
}
file := astutil.EnclosingFile(curCall)
if isIndex(compare.X, i) && isIndex(compare.Y, j) &&
analyzerutil.FileUsesGoVersion(pass, file, versions.Go1_21) {
// Have: sort.Slice(s, func(i, j int) bool { return s[i] < s[j] })
prefix, importEdits := refactor.AddImport(
info, file, "slices", "slices", "Sort", call.Pos())
pass.Report(analysis.Diagnostic{
// Highlight "sort.Slice".
Pos: call.Fun.Pos(),
End: call.Fun.End(),
Message: "sort.Slice can be modernized using slices.Sort",
SuggestedFixes: []analysis.SuggestedFix{{
Message: "Replace sort.Slice call by slices.Sort",
TextEdits: append(importEdits, []analysis.TextEdit{
{
// Replace sort.Slice with slices.Sort.
Pos: call.Fun.Pos(),
End: call.Fun.End(),
NewText: []byte(prefix + "Sort"),
},
{
// Eliminate FuncLit.
Pos: call.Args[0].End(),
End: call.Rparen,
},
}...),
}},
})
}
}
}
}
}
return nil, nil
}
================================================
FILE: go/analysis/passes/modernize/stditerators.go
================================================
// Copyright 2025 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package modernize
import (
"fmt"
"go/ast"
"go/token"
"go/types"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/ast/edge"
"golang.org/x/tools/go/ast/inspector"
"golang.org/x/tools/go/types/typeutil"
"golang.org/x/tools/internal/analysis/analyzerutil"
typeindexanalyzer "golang.org/x/tools/internal/analysis/typeindex"
"golang.org/x/tools/internal/astutil"
"golang.org/x/tools/internal/goplsexport"
"golang.org/x/tools/internal/stdlib"
"golang.org/x/tools/internal/typesinternal/typeindex"
)
var stditeratorsAnalyzer = &analysis.Analyzer{
Name: "stditerators",
Doc: analyzerutil.MustExtractDoc(doc, "stditerators"),
Requires: []*analysis.Analyzer{
typeindexanalyzer.Analyzer,
},
Run: stditerators,
URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/modernize#stditerators",
}
func init() {
// Export to gopls until this is a published modernizer.
goplsexport.StdIteratorsModernizer = stditeratorsAnalyzer
}
// stditeratorsTable records std types that have legacy T.{Len,At}
// iteration methods as well as a newer T.All method that returns an
// iter.Seq.
var stditeratorsTable = [...]struct {
pkgpath, typename, lenmethod, atmethod, itermethod, elemname string
seqn int // 1 or 2 => "for x" or "for _, x"
}{
// Example: in go/types, (*Tuple).Variables returns an
// iterator that replaces a loop over (*Tuple).{Len,At}.
// The loop variable is named "v".
{"go/types", "Interface", "NumEmbeddeds", "EmbeddedType", "EmbeddedTypes", "etyp", 1},
{"go/types", "Interface", "NumExplicitMethods", "ExplicitMethod", "ExplicitMethods", "method", 1},
{"go/types", "Interface", "NumMethods", "Method", "Methods", "method", 1},
{"go/types", "MethodSet", "Len", "At", "Methods", "method", 1},
{"go/types", "Named", "NumMethods", "Method", "Methods", "method", 1},
{"go/types", "Scope", "NumChildren", "Child", "Children", "child", 1},
{"go/types", "Struct", "NumFields", "Field", "Fields", "field", 1},
{"go/types", "Tuple", "Len", "At", "Variables", "v", 1},
{"go/types", "TypeList", "Len", "At", "Types", "t", 1},
{"go/types", "TypeParamList", "Len", "At", "TypeParams", "tparam", 1},
{"go/types", "Union", "Len", "Term", "Terms", "term", 1},
{"reflect", "Type", "NumField", "Field", "Fields", "field", 1},
{"reflect", "Type", "NumMethod", "Method", "Methods", "method", 1},
{"reflect", "Type", "NumIn", "In", "Ins", "in", 1},
{"reflect", "Type", "NumOut", "Out", "Outs", "out", 1},
{"reflect", "Value", "NumField", "Field", "Fields", "field", 2},
{"reflect", "Value", "NumMethod", "Method", "Methods", "method", 2},
}
// stditerators suggests fixes to replace loops using Len/At-style
// iterator APIs by a range loop over an iterator. The set of
// participating types and methods is defined by [iteratorsTable].
//
// Pattern:
//
// for i := 0; i < x.Len(); i++ {
// use(x.At(i))
// }
//
// =>
//
// for elem := range x.All() {
// use(elem)
// }
//
// Variant:
//
// for i := range x.Len() { ... }
//
// Note: Iterators have a dynamic cost. How do we know that
// the user hasn't intentionally chosen not to use an
// iterator for that reason? We don't want to go fix to
// undo optimizations. Do we need a suppression mechanism?
//
// TODO(adonovan): recognize the more complex patterns that
// could make full use of both components of an iter.Seq2, e.g.
//
// for i := 0; i < v.NumField(); i++ {
// use(v.Field(i), v.Type().Field(i))
// }
//
// =>
//
// for structField, field := range v.Fields() {
// use(structField, field)
// }
func stditerators(pass *analysis.Pass) (any, error) {
var (
index = pass.ResultOf[typeindexanalyzer.Analyzer].(*typeindex.Index)
info = pass.TypesInfo
)
for _, row := range stditeratorsTable {
// Don't offer fixes within the package
// that defines the iterator in question.
if within(pass, row.pkgpath) {
continue
}
var (
lenMethod = index.Selection(row.pkgpath, row.typename, row.lenmethod)
atMethod = index.Selection(row.pkgpath, row.typename, row.atmethod)
)
// chooseName returns an appropriate fresh name
// for the index variable of the iterator loop
// whose body is specified.
//
// If the loop body starts with
//
// for ... { e := x.At(i); use(e) }
//
// or
//
// for ... { if e := x.At(i); cond { use(e) } }
//
// then chooseName prefers the name e and additionally
// returns the var's symbol. We'll transform this to:
//
// for e := range x.Len() { e := e; use(e) }
//
// which leaves a redundant assignment that a
// subsequent 'forvar' pass will eliminate.
chooseName := func(curBody inspector.Cursor, x ast.Expr, i *types.Var) (string, *types.Var) {
// isVarAssign reports whether stmt has the form v := x.At(i)
// and returns the variable if so.
isVarAssign := func(stmt ast.Stmt) *types.Var {
if assign, ok := stmt.(*ast.AssignStmt); ok &&
assign.Tok == token.DEFINE &&
len(assign.Lhs) == 1 &&
len(assign.Rhs) == 1 &&
is[*ast.Ident](assign.Lhs[0]) {
// call to x.At(i)?
if call, ok := assign.Rhs[0].(*ast.CallExpr); ok &&
typeutil.Callee(info, call) == atMethod &&
astutil.EqualSyntax(ast.Unparen(call.Fun).(*ast.SelectorExpr).X, x) &&
is[*ast.Ident](call.Args[0]) &&
info.Uses[call.Args[0].(*ast.Ident)] == i {
// Have: elem := x.At(i)
id := assign.Lhs[0].(*ast.Ident)
return info.Defs[id].(*types.Var)
}
}
return nil
}
body := curBody.Node().(*ast.BlockStmt)
if len(body.List) > 0 {
// Is body { elem := x.At(i); ... } ?
if v := isVarAssign(body.List[0]); v != nil {
return v.Name(), v
}
// Or { if elem := x.At(i); cond { ... } } ?
if ifstmt, ok := body.List[0].(*ast.IfStmt); ok && ifstmt.Init != nil {
if v := isVarAssign(ifstmt.Init); v != nil {
return v.Name(), v
}
}
}
loop := curBody.Parent().Node()
// We generate a new name only if the preferred name is already declared here
// and is used within the loop body.
name := freshName(info, index, info.Scopes[loop], loop.Pos(), curBody, curBody, token.NoPos, row.elemname)
return name, nil
}
// Process each call of x.Len().
nextCall:
for curLenCall := range index.Calls(lenMethod) {
lenSel, ok := ast.Unparen(curLenCall.Node().(*ast.CallExpr).Fun).(*ast.SelectorExpr)
if !ok {
continue
}
// lenSel is "x.Len"
var (
rng analysis.Range // where to report diagnostic
curBody inspector.Cursor // loop body
indexVar *types.Var // old loop index var
elemVar *types.Var // existing "elem := x.At(i)" var, if present
elem string // name for new loop var
edits []analysis.TextEdit
)
// Analyze enclosing loop.
switch curLenCall.ParentEdgeKind() {
case edge.BinaryExpr_Y:
// pattern 1: for i := 0; i < x.Len(); i++ { ... }
var (
curCmp = curLenCall.Parent()
cmp = curCmp.Node().(*ast.BinaryExpr)
)
if cmp.Op != token.LSS ||
curCmp.ParentEdgeKind() != edge.ForStmt_Cond {
continue
}
if id, ok := cmp.X.(*ast.Ident); ok {
// Have: for _; i < x.Len(); _ { ... }
var (
v = info.Uses[id].(*types.Var)
curFor = curCmp.Parent()
loop = curFor.Node().(*ast.ForStmt)
)
if v != isIncrementLoop(info, loop) {
continue
}
// Have: for i := 0; i < x.Len(); i++ { ... }.
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
rng = astutil.RangeOf(loop.For, loop.Post.End())
indexVar = v
curBody = curFor.ChildAt(edge.ForStmt_Body, -1)
elem, elemVar = chooseName(curBody, lenSel.X, indexVar)
elemPrefix := cond(row.seqn == 2, "_, ", "")
// for i := 0; i < x.Len(); i++ {
// ---- ------- --- -----
// for elem := range x.All() {
// or for _, elem := ...
edits = []analysis.TextEdit{
{
Pos: v.Pos(),
End: v.Pos() + token.Pos(len(v.Name())),
NewText: []byte(elemPrefix + elem),
},
{
Pos: loop.Init.(*ast.AssignStmt).Rhs[0].Pos(),
End: cmp.Y.Pos(),
NewText: []byte("range "),
},
{
Pos: lenSel.Sel.Pos(),
End: lenSel.Sel.End(),
NewText: []byte(row.itermethod),
},
{
Pos: curLenCall.Node().End(),
End: loop.Post.End(),
},
}
}
case edge.RangeStmt_X:
// pattern 2: for i := range x.Len() { ... }
var (
curRange = curLenCall.Parent()
loop = curRange.Node().(*ast.RangeStmt)
)
if id, ok := loop.Key.(*ast.Ident); ok &&
loop.Value == nil &&
loop.Tok == token.DEFINE {
// Have: for i := range x.Len() { ... }
// ~~~~~~~~~~~~~
rng = astutil.RangeOf(loop.Range, loop.X.End())
indexVar = info.Defs[id].(*types.Var)
curBody = curRange.ChildAt(edge.RangeStmt_Body, -1)
elem, elemVar = chooseName(curBody, lenSel.X, indexVar)
elemPrefix := cond(row.seqn == 2, "_, ", "")
// for i := range x.Len() {
// ---- ---
// for elem := range x.All() {
edits = []analysis.TextEdit{
{
Pos: loop.Key.Pos(),
End: loop.Key.End(),
NewText: []byte(elemPrefix + elem),
},
{
Pos: lenSel.Sel.Pos(),
End: lenSel.Sel.End(),
NewText: []byte(row.itermethod),
},
}
}
}
if indexVar == nil {
continue // no loop of the required form
}
// TODO(adonovan): what about possible
// modifications of x within the loop?
// Aliasing seems to make a conservative
// treatment impossible.
// Check that all uses of var i within loop body are x.At(i).
for curUse := range index.Uses(indexVar) {
if !curBody.Contains(curUse) {
continue
}
if ek, argidx := curUse.ParentEdge(); ek != edge.CallExpr_Args || argidx != 0 {
continue nextCall // use is not arg of call
}
curAtCall := curUse.Parent()
atCall := curAtCall.Node().(*ast.CallExpr)
if typeutil.Callee(info, atCall) != atMethod {
continue nextCall // use is not arg of call to T.At
}
atSel := ast.Unparen(atCall.Fun).(*ast.SelectorExpr)
// Check receivers of Len, At calls match (syntactically).
if !astutil.EqualSyntax(lenSel.X, atSel.X) {
continue nextCall
}
// At each point of use, check that
// the fresh variable is not shadowed
// by an intervening local declaration
// (or by the idiomatic elemVar optionally
// found by chooseName).
if obj := lookup(info, curAtCall, elem); obj != nil && obj != elemVar && obj.Pos() > indexVar.Pos() {
// (Ideally, instead of giving up, we would
// embellish the name and try again.)
continue nextCall
}
// use(x.At(i))
// -------
// use(elem )
edits = append(edits, analysis.TextEdit{
Pos: atCall.Pos(),
End: atCall.End(),
NewText: []byte(elem),
})
}
// Check file Go version is new enough for the iterator method.
// (In the long run, version filters are not highly selective,
// so there's no need to do them first, especially as this check
// may be somewhat expensive.)
if v, err := methodGoVersion(row.pkgpath, row.typename, row.itermethod); err != nil {
panic(err)
} else if !analyzerutil.FileUsesGoVersion(pass, astutil.EnclosingFile(curLenCall), v.String()) {
continue nextCall
}
pass.Report(analysis.Diagnostic{
Pos: rng.Pos(),
End: rng.End(),
Message: fmt.Sprintf("%s/%s loop can simplified using %s.%s iteration",
row.lenmethod, row.atmethod, row.typename, row.itermethod),
SuggestedFixes: []analysis.SuggestedFix{{
Message: fmt.Sprintf(
"Replace %s/%s loop with %s.%s iteration",
row.lenmethod, row.atmethod, row.typename, row.itermethod),
TextEdits: edits,
}},
})
}
}
return nil, nil
}
// -- helpers --
// methodGoVersion reports the version at which the method
// (pkgpath.recvtype).method appeared in the standard library.
func methodGoVersion(pkgpath, recvtype, method string) (stdlib.Version, error) {
// TODO(adonovan): opt: this might be inefficient for large packages
// like go/types. If so, memoize using a map (and kill two birds with
// one stone by also memoizing the 'within' check above).
for _, sym := range stdlib.PackageSymbols[pkgpath] {
if sym.Kind == stdlib.Method {
_, recv, name := sym.SplitMethod()
if recv == recvtype && name == method {
return sym.Version, nil
}
}
}
return 0, fmt.Errorf("methodGoVersion: %s.%s.%s missing from stdlib manifest", pkgpath, recvtype, method)
}
================================================
FILE: go/analysis/passes/modernize/stringsbuilder.go
================================================
// Copyright 2024 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package modernize
import (
"cmp"
"fmt"
"go/ast"
"go/constant"
"go/token"
"go/types"
"maps"
"slices"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/ast/edge"
"golang.org/x/tools/go/ast/inspector"
"golang.org/x/tools/internal/analysis/analyzerutil"
typeindexanalyzer "golang.org/x/tools/internal/analysis/typeindex"
"golang.org/x/tools/internal/astutil"
"golang.org/x/tools/internal/refactor"
"golang.org/x/tools/internal/typesinternal/typeindex"
)
var StringsBuilderAnalyzer = &analysis.Analyzer{
Name: "stringsbuilder",
Doc: analyzerutil.MustExtractDoc(doc, "stringsbuilder"),
Requires: []*analysis.Analyzer{
inspect.Analyzer,
typeindexanalyzer.Analyzer,
},
Run: stringsbuilder,
URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/modernize#stringbuilder",
}
// stringsbuilder replaces string += string in a loop by strings.Builder.
func stringsbuilder(pass *analysis.Pass) (any, error) {
// Skip the analyzer in packages where its
// fixes would create an import cycle.
if within(pass, "strings", "runtime") {
return nil, nil
}
var (
inspect = pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
index = pass.ResultOf[typeindexanalyzer.Analyzer].(*typeindex.Index)
)
// Gather all local string variables that appear on the
// LHS of some string += string assignment.
candidates := make(map[*types.Var]bool)
for curAssign := range inspect.Root().Preorder((*ast.AssignStmt)(nil)) {
assign := curAssign.Node().(*ast.AssignStmt)
if assign.Tok == token.ADD_ASSIGN && is[*ast.Ident](assign.Lhs[0]) {
if v, ok := pass.TypesInfo.Uses[assign.Lhs[0].(*ast.Ident)].(*types.Var); ok &&
v.Kind() == types.LocalVar &&
types.Identical(v.Type(), builtinString.Type()) {
candidates[v] = true
}
}
}
lexicalOrder := func(x, y *types.Var) int { return cmp.Compare(x.Pos(), y.Pos()) }
// File and Pos of last fix edit,
// for overlapping fix span detection.
var (
lastEditFile *ast.File
lastEditEnd token.Pos
)
// Now check each candidate variable's decl and uses.
nextcand:
for _, v := range slices.SortedFunc(maps.Keys(candidates), lexicalOrder) {
var edits []analysis.TextEdit
// Check declaration of s has one of these forms:
//
// s := expr
// var s [string] [= expr]
// var ( ...; s [string] [= expr] ) (s is last)
//
// and transform to one of:
//
// var s strings.Builder ; s.WriteString(expr)
// var ( s strings.Builder); s.WriteString(expr)
//
def, ok := index.Def(v)
if !ok {
continue
}
// To avoid semantic conflicts, do not offer a fix if its edit
// range (ignoring import edits) overlaps a previous fix.
// This fixes #76983 and is an ad-hoc mitigation of #76476.
file := astutil.EnclosingFile(def)
if file == lastEditFile && v.Pos() < lastEditEnd {
continue
}
ek := def.ParentEdgeKind()
if ek == edge.AssignStmt_Lhs &&
len(def.Parent().Node().(*ast.AssignStmt).Lhs) == 1 {
// Have: s := expr
// => var s strings.Builder; s.WriteString(expr)
assign := def.Parent().Node().(*ast.AssignStmt)
// Reject "if s := f(); ..." since in that context
// we can't replace the assign with two statements.
switch def.Parent().Parent().Node().(type) {
case *ast.BlockStmt, *ast.CaseClause, *ast.CommClause:
// OK: these are the parts of syntax that
// allow unrestricted statement lists.
default:
continue
}
// Add strings import.
prefix, importEdits := refactor.AddImport(
pass.TypesInfo, astutil.EnclosingFile(def), "strings", "strings", "Builder", v.Pos())
edits = append(edits, importEdits...)
if isEmptyString(pass.TypesInfo, assign.Rhs[0]) {
// s := ""
// ---------------------
// var s strings.Builder
edits = append(edits, analysis.TextEdit{
Pos: assign.Pos(),
End: assign.End(),
NewText: fmt.Appendf(nil, "var %[1]s %[2]sBuilder", v.Name(), prefix),
})
} else {
// s := expr
// ------------------------------------- -
// var s strings.Builder; s.WriteString(expr)
edits = append(edits, []analysis.TextEdit{
{
Pos: assign.Pos(),
End: assign.Rhs[0].Pos(),
NewText: fmt.Appendf(nil, "var %[1]s %[2]sBuilder; %[1]s.WriteString(", v.Name(), prefix),
},
{
Pos: assign.End(),
End: assign.End(),
NewText: []byte(")"),
},
}...)
}
} else if ek == edge.ValueSpec_Names &&
len(def.Parent().Node().(*ast.ValueSpec).Names) == 1 &&
first(def.Parent().Parent().LastChild()) == def.Parent() {
// Have: var s [string] [= expr]
// or: var ( s [string] [= expr] )
// => var s strings.Builder; s.WriteString(expr)
//
// The LastChild check rejects this case:
// var ( s [string] [= expr]; others... )
// =>
// var ( s strings.Builder; others... ); s.WriteString(expr)
// since it moves 'expr' across 'others', requiring
// reformatting of syntax, which in general is lossy
// of comments and vertical space.
// We expect this to be rare.
// Add strings import.
prefix, importEdits := refactor.AddImport(
pass.TypesInfo, astutil.EnclosingFile(def), "strings", "strings", "Builder", v.Pos())
edits = append(edits, importEdits...)
spec := def.Parent().Node().(*ast.ValueSpec)
decl := def.Parent().Parent().Node().(*ast.GenDecl)
init := spec.Names[0].End() // start of " = expr"
if spec.Type != nil {
init = spec.Type.End()
}
// Replace (possibly absent) type:
//
// var s [string]
// ----------------
// var s strings.Builder
edits = append(edits, analysis.TextEdit{
Pos: spec.Names[0].End(),
End: init,
NewText: fmt.Appendf(nil, " %sBuilder", prefix),
})
if len(spec.Values) > 0 && !isEmptyString(pass.TypesInfo, spec.Values[0]) {
if decl.Rparen.IsValid() {
// var decl with explicit parens:
//
// var ( ... = expr )
// - -
// var ( ... ); s.WriteString(expr)
edits = append(edits, []analysis.TextEdit{
{
Pos: init,
End: init,
NewText: []byte(")"),
},
{
Pos: spec.Values[0].End(),
End: decl.End(),
},
}...)
}
// = expr
// ---------------- -
// ; s.WriteString(expr)
edits = append(edits, []analysis.TextEdit{
{
Pos: init,
End: spec.Values[0].Pos(),
NewText: fmt.Appendf(nil, "; %s.WriteString(", v.Name()),
},
{
Pos: spec.Values[0].End(),
End: spec.Values[0].End(),
NewText: []byte(")"),
},
}...)
} else {
// delete "= expr"
edits = append(edits, analysis.TextEdit{
Pos: init,
End: spec.End(),
})
}
} else {
continue
}
// Check uses of s.
//
// - All uses of s except the final one must be of the form
//
// s += expr
//
// Each of these will become s.WriteString(expr).
// At least one of them must be in an intervening loop
// w.r.t. the declaration of s:
//
// var s string
// for ... { s += expr }
//
// - All uses of s after the last += must be rvalue uses (e.g. use(s), not &s).
// Each of these will become s.String().
//
// Perhaps surprisingly, it is fine for there to be an
// intervening loop or lambda w.r.t. the declaration of s:
//
// var s strings.Builder
// for range kSmall { s.WriteString(expr) }
// for range kLarge { use(s.String()) } // called repeatedly
//
// Even though that might cause the s.String() operation to be
// executed repeatedly, this is not a deoptimization because,
// by design, (*strings.Builder).String does not allocate.
var (
numLoopAssigns int // number of += assignments within a loop
loopAssign *ast.AssignStmt // first += assignment within a loop
seenRvalueUse bool // => we've seen at least one rvalue use of s
)
for curUse := range index.Uses(v) {
// Strip enclosing parens around Ident.
ek := curUse.ParentEdgeKind()
for ek == edge.ParenExpr_X {
curUse = curUse.Parent()
ek = curUse.ParentEdgeKind()
}
// intervening reports whether cur has an ancestor of
// one of the given types that is within the scope of v.
intervening := func(types ...ast.Node) bool {
for cur := range curUse.Enclosing(types...) {
if v.Pos() <= cur.Node().Pos() { // in scope of v
return true
}
}
return false
}
if ek == edge.AssignStmt_Lhs {
// After an rvalue use, no more assignments are allowed.
if seenRvalueUse {
continue nextcand
}
assign := curUse.Parent().Node().(*ast.AssignStmt)
if assign.Tok != token.ADD_ASSIGN {
continue nextcand
}
// Have: s += expr
// At least one of the += operations
// must appear within a loop.
// relative to the declaration of s.
if intervening((*ast.ForStmt)(nil), (*ast.RangeStmt)(nil)) {
numLoopAssigns++
if loopAssign == nil {
loopAssign = assign
}
}
// s += expr
// ------------- -
// s.WriteString(expr)
edits = append(edits, []analysis.TextEdit{
// replace " += " with ".WriteString("
{
Pos: assign.Lhs[0].End(),
End: assign.Rhs[0].Pos(),
NewText: []byte(".WriteString("),
},
// insert ")"
{
Pos: assign.End(),
End: assign.End(),
NewText: []byte(")"),
},
}...)
} else if ek == edge.UnaryExpr_X &&
curUse.Parent().Node().(*ast.UnaryExpr).Op == token.AND {
// Have: use(&s)
continue nextcand // s is used as an lvalue; reject
} else {
// The only possible l-value uses of a string variable
// are assignments (s=expr, s+=expr, etc) and &s.
// (For strings, we can ignore method calls s.m().)
// All other uses are r-values.
seenRvalueUse = true
edits = append(edits, analysis.TextEdit{
// insert ".String()"
Pos: curUse.Node().End(),
End: curUse.Node().End(),
NewText: []byte(".String()"),
})
}
}
if !seenRvalueUse {
continue nextcand // no rvalue use; reject
}
if numLoopAssigns == 0 {
continue nextcand // no += in a loop; reject
}
lastEditFile = file
lastEditEnd = edits[len(edits)-1].End
pass.Report(analysis.Diagnostic{
Pos: loopAssign.Pos(),
End: loopAssign.End(),
Message: "using string += string in a loop is inefficient",
SuggestedFixes: []analysis.SuggestedFix{{
Message: "Replace string += string with strings.Builder",
TextEdits: edits,
}},
})
}
return nil, nil
}
// isEmptyString reports whether e (a string-typed expression) has constant value "".
func isEmptyString(info *types.Info, e ast.Expr) bool {
tv, ok := info.Types[e]
return ok && tv.Value != nil && constant.StringVal(tv.Value) == ""
}
================================================
FILE: go/analysis/passes/modernize/stringscut.go
================================================
// Copyright 2025 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package modernize
import (
"fmt"
"go/ast"
"go/constant"
"go/token"
"go/types"
"iter"
"strconv"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/ast/edge"
"golang.org/x/tools/go/ast/inspector"
"golang.org/x/tools/go/types/typeutil"
"golang.org/x/tools/internal/analysis/analyzerutil"
typeindexanalyzer "golang.org/x/tools/internal/analysis/typeindex"
"golang.org/x/tools/internal/astutil"
"golang.org/x/tools/internal/goplsexport"
"golang.org/x/tools/internal/moreiters"
"golang.org/x/tools/internal/typesinternal"
"golang.org/x/tools/internal/typesinternal/typeindex"
"golang.org/x/tools/internal/versions"
)
var stringscutAnalyzer = &analysis.Analyzer{
Name: "stringscut",
Doc: analyzerutil.MustExtractDoc(doc, "stringscut"),
Requires: []*analysis.Analyzer{
inspect.Analyzer,
typeindexanalyzer.Analyzer,
},
Run: stringscut,
URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/modernize#stringscut",
}
func init() {
// Export to gopls until this is a published modernizer.
goplsexport.StringsCutModernizer = stringscutAnalyzer
}
// stringscut offers a fix to replace an occurrence of strings.Index{,Byte} with
// strings.{Cut,Contains}, and similar fixes for functions in the bytes package.
// Consider some candidate for replacement i := strings.Index(s, substr).
// The following must hold for a replacement to occur:
//
// 1. All instances of i and s must be in one of these forms.
//
// Binary expressions must be inequalities equivalent to
// "Index failed" (e.g. i < 0) or "Index succeeded" (i >= 0),
// or identities such as these (and their negations):
//
// 0 > i (flips left and right)
// i <= -1, -1 >= i (replace strict inequality by non-strict)
// i == -1, -1 == i (Index() guarantees i < 0 => i == -1)
//
// Slice expressions:
// a: s[:i], s[0:i]
// b: s[i+len(substr):], s[len(substr) + i:], s[i + const], s[k + i] (where k = len(substr))
//
// 2. There can be no uses of s, substr, or i where they are
// potentially modified (i.e. in assignments, or function calls with unknown side
// effects).
//
// Then, the replacement involves the following substitutions:
//
// 1. Replace "i := strings.Index(s, substr)" with "before, after, ok := strings.Cut(s, substr)"
//
// 2. Replace instances of binary expressions (a) with !ok and binary expressions (b) with ok.
//
// 3. Replace slice expressions (a) with "before" and slice expressions (b) with after.
//
// 4. The assignments to before, after, and ok may use the blank identifier "_" if they are unused.
//
// For example:
//
// i := strings.Index(s, substr)
// if i >= 0 {
// use(s[:i], s[i+len(substr):])
// }
//
// Would become:
//
// before, after, ok := strings.Cut(s, substr)
// if ok {
// use(before, after)
// }
//
// If the condition involving `i` is equivalent to i >= 0, then we replace it with
// `if ok“.
// If the condition is negated (e.g. equivalent to `i < 0`), we use `if !ok` instead.
// If the slices of `s` match `s[:i]` or `s[i+len(substr):]` or their variants listed above,
// then we replace them with before and after.
//
// When the index `i` is used only to check for the presence of the substring or byte slice,
// the suggested fix uses Contains() instead of Cut.
//
// For example:
//
// i := strings.Index(s, substr)
// if i >= 0 {
// return
// }
//
// Would become:
//
// found := strings.Contains(s, substr)
// if found {
// return
// }
func stringscut(pass *analysis.Pass) (any, error) {
var (
index = pass.ResultOf[typeindexanalyzer.Analyzer].(*typeindex.Index)
info = pass.TypesInfo
stringsIndex = index.Object("strings", "Index")
stringsIndexByte = index.Object("strings", "IndexByte")
bytesIndex = index.Object("bytes", "Index")
bytesIndexByte = index.Object("bytes", "IndexByte")
)
scopeFixCount := make(map[*types.Scope]int) // the number of times we have offered a fix within a given scope in the current pass
for _, obj := range []types.Object{
stringsIndex,
stringsIndexByte,
bytesIndex,
bytesIndexByte,
} {
// (obj may be nil)
nextcall:
for curCall := range index.Calls(obj) {
// Check file version.
if !analyzerutil.FileUsesGoVersion(pass, astutil.EnclosingFile(curCall), versions.Go1_18) {
continue // strings.Index not available in this file
}
indexCall := curCall.Node().(*ast.CallExpr) // the call to strings.Index, etc.
obj := typeutil.Callee(info, indexCall)
if obj == nil {
continue
}
var iIdent *ast.Ident // defining identifier of i var
switch ek, idx := curCall.ParentEdge(); ek {
case edge.ValueSpec_Values:
// Have: var i = strings.Index(...)
curName := curCall.Parent().ChildAt(edge.ValueSpec_Names, idx)
iIdent = curName.Node().(*ast.Ident)
case edge.AssignStmt_Rhs:
// Have: i := strings.Index(...)
// (Must be i's definition.)
curLhs := curCall.Parent().ChildAt(edge.AssignStmt_Lhs, idx)
iIdent, _ = curLhs.Node().(*ast.Ident) // may be nil
}
if iIdent == nil {
continue
}
// Inv: iIdent is i's definition. The following would be skipped: 'var i int; i = strings.Index(...)'
// Get uses of i.
iObj := info.ObjectOf(iIdent)
if iObj == nil {
continue
}
var (
s = indexCall.Args[0]
substr = indexCall.Args[1]
)
// Check that there are no statements that alter the value of s
// or substr after the call to Index().
if !indexArgValid(info, index, s, indexCall.Pos()) ||
!indexArgValid(info, index, substr, indexCall.Pos()) {
continue nextcall
}
// Next, examine all uses of i. If the only uses are of the
// forms mentioned above (e.g. i < 0, i >= 0, s[:i] and s[i +
// len(substr)]), then we can replace the call to Index()
// with a call to Cut() and use the returned ok, before,
// and after variables accordingly.
negative, nonnegative, beforeSlice, afterSlice := checkIdxUses(pass.TypesInfo, index.Uses(iObj), s, substr, iObj)
// Either there are no uses of before, after, or ok, or some use
// of i does not match our criteria - don't suggest a fix.
if negative == nil && nonnegative == nil && beforeSlice == nil && afterSlice == nil {
continue
}
// If the only uses are ok and !ok, don't suggest a Cut() fix - these should be using Contains()
isContains := (len(negative) > 0 || len(nonnegative) > 0) && len(beforeSlice) == 0 && len(afterSlice) == 0
enclosingBlock, ok := moreiters.First(curCall.Enclosing((*ast.BlockStmt)(nil)))
if !ok {
continue
}
scope := iObj.Parent()
// Generate fresh names for ok, before, after, found, but only if
// they are defined by the end of the enclosing block and used
// within the enclosing block after the Index call. We need a Cursor
// for the end of the enclosing block, but we can't just find the
// Cursor at scope.End() because it corresponds to the entire
// enclosingBlock. Instead, get the last child of the enclosing
// block.
lastStmtCur, _ := enclosingBlock.LastChild()
lastStmt := lastStmtCur.Node()
fresh := func(preferred string) string {
return freshName(info, index, scope, lastStmt.End(), lastStmtCur, enclosingBlock, iIdent.Pos(), preferred)
}
var okVarName, beforeVarName, afterVarName, foundVarName string
if isContains {
foundVarName = fresh("found")
} else {
okVarName = fresh("ok")
beforeVarName = fresh("before")
afterVarName = fresh("after")
}
// If we are already suggesting a fix within the index's scope, we
// must get fresh names for before, after and ok.
// This is a specific symptom of the general problem that analyzers
// can generate conflicting fixes.
if scopeFixCount[scope] > 0 {
suffix := scopeFixCount[scope] - 1 // start at 0
if isContains {
foundVarName = fresh(fmt.Sprintf("%s%d", foundVarName, suffix))
} else {
okVarName = fresh(fmt.Sprintf("%s%d", okVarName, suffix))
beforeVarName = fresh(fmt.Sprintf("%s%d", beforeVarName, suffix))
afterVarName = fresh(fmt.Sprintf("%s%d", afterVarName, suffix))
}
}
// If there will be no uses of ok, before, or after, use the
// blank identifier instead.
if len(negative) == 0 && len(nonnegative) == 0 {
okVarName = "_"
}
if len(beforeSlice) == 0 {
beforeVarName = "_"
}
if len(afterSlice) == 0 {
afterVarName = "_"
}
var edits []analysis.TextEdit
replace := func(exprs []ast.Expr, new string) {
for _, expr := range exprs {
edits = append(edits, analysis.TextEdit{
Pos: expr.Pos(),
End: expr.End(),
NewText: []byte(new),
})
}
}
// Get the ident for the call to strings.Index, which could just be
// "Index" if the strings package is dot imported.
indexCallId := typesinternal.UsedIdent(info, indexCall.Fun)
replacedFunc := "Cut"
if isContains {
replacedFunc = "Contains"
replace(negative, "!"+foundVarName) // idx < 0 -> !found
replace(nonnegative, foundVarName) // idx > -1 -> found
// Replace the assignment with found, and replace the call to
// Index or IndexByte with a call to Contains.
// i := strings.Index (...)
// ----- --------
// found := strings.Contains(...)
edits = append(edits, analysis.TextEdit{
Pos: iIdent.Pos(),
End: iIdent.End(),
NewText: []byte(foundVarName),
}, analysis.TextEdit{
Pos: indexCallId.Pos(),
End: indexCallId.End(),
NewText: []byte("Contains"),
})
} else {
replace(negative, "!"+okVarName) // idx < 0 -> !ok
replace(nonnegative, okVarName) // idx > -1 -> ok
replace(beforeSlice, beforeVarName) // s[:idx] -> before
replace(afterSlice, afterVarName) // s[idx+k:] -> after
// Replace the assignment with before, after, ok, and replace
// the call to Index or IndexByte with a call to Cut.
// i := strings.Index(...)
// ----------------- -----
// before, after, ok := strings.Cut (...)
edits = append(edits, analysis.TextEdit{
Pos: iIdent.Pos(),
End: iIdent.End(),
NewText: fmt.Appendf(nil, "%s, %s, %s", beforeVarName, afterVarName, okVarName),
}, analysis.TextEdit{
Pos: indexCallId.Pos(),
End: indexCallId.End(),
NewText: []byte("Cut"),
})
}
// Calls to IndexByte have a byte as their second arg, which
// must be converted to a string or []byte to be a valid arg for Cut/Contains.
if obj.Name() == "IndexByte" {
switch obj.Pkg().Name() {
case "strings":
searchByteVal := info.Types[substr].Value
if searchByteVal == nil {
// substr is a variable, e.g. substr := byte('b')
// use string(substr)
edits = append(edits, []analysis.TextEdit{
{
Pos: substr.Pos(),
NewText: []byte("string("),
},
{
Pos: substr.End(),
NewText: []byte(")"),
},
}...)
} else {
// substr is a byte constant
val, _ := constant.Int64Val(searchByteVal) // inv: must be a valid byte
// strings.Cut/Contains requires a string, so convert byte literal to string literal; e.g. 'a' -> "a", 55 -> "7"
edits = append(edits, analysis.TextEdit{
Pos: substr.Pos(),
End: substr.End(),
NewText: strconv.AppendQuote(nil, string(byte(val))),
})
}
case "bytes":
// bytes.Cut/Contains requires a []byte, so wrap substr in a []byte{}
edits = append(edits, []analysis.TextEdit{
{
Pos: substr.Pos(),
NewText: []byte("[]byte{"),
},
{
Pos: substr.End(),
NewText: []byte("}"),
},
}...)
}
}
scopeFixCount[scope]++
pass.Report(analysis.Diagnostic{
Pos: indexCall.Fun.Pos(),
End: indexCall.Fun.End(),
Message: fmt.Sprintf("%s.%s can be simplified using %s.%s",
obj.Pkg().Name(), obj.Name(), obj.Pkg().Name(), replacedFunc),
Category: "stringscut",
SuggestedFixes: []analysis.SuggestedFix{{
Message: fmt.Sprintf("Simplify %s.%s call using %s.%s", obj.Pkg().Name(), obj.Name(), obj.Pkg().Name(), replacedFunc),
TextEdits: edits,
}},
})
}
}
return nil, nil
}
// indexArgValid reports whether expr is a valid strings.Index(_, _) arg
// for the transformation. An arg is valid iff it is:
// - constant;
// - a local variable with no modifying uses after the Index() call; or
// - []byte(x) where x is also valid by this definition.
// All other expressions are assumed not referentially transparent,
// so we cannot be sure that all uses are safe to replace.
func indexArgValid(info *types.Info, index *typeindex.Index, expr ast.Expr, afterPos token.Pos) bool {
tv := info.Types[expr]
if tv.Value != nil {
return true // constant
}
switch expr := expr.(type) {
case *ast.CallExpr:
return types.Identical(tv.Type, byteSliceType) &&
info.Types[expr.Fun].IsType() && // make sure this isn't a function that returns a byte slice
indexArgValid(info, index, expr.Args[0], afterPos) // check s in []byte(s)
case *ast.Ident:
sObj := info.Uses[expr]
sUses := index.Uses(sObj)
return !hasModifyingUses(info, sUses, afterPos)
default:
// For now, skip instances where s or substr are not
// identifers, basic lits, or call expressions of the form
// []byte(s).
// TODO(mkalil): Handle s and substr being expressions like ptr.field[i].
// From adonovan: We'd need to analyze s and substr to see
// whether they are referentially transparent, and if not,
// analyze all code between declaration and use and see if
// there are statements or expressions with potential side
// effects.
return false
}
}
// checkIdxUses inspects the uses of i to make sure they match certain criteria that
// allows us to suggest a modernization. If all uses of i, s and substr match
// one of the following four valid formats, it returns a list of occurrences for
// each format. If any of the uses do not match one of the formats, return nil
// for all values, since we should not offer a replacement.
// 1. negative - a condition equivalent to i < 0
// 2. nonnegative - a condition equivalent to i >= 0
// 3. beforeSlice - a slice of `s` that matches either s[:i], s[0:i]
// 4. afterSlice - a slice of `s` that matches one of: s[i+len(substr):], s[len(substr) + i:], s[i + const], s[k + i] (where k = len(substr))
//
// Additionally, all beforeSlice and afterSlice uses must be dominated by a
// nonnegative guard on i (i.e., inside the body of an if whose condition
// checks i >= 0, or in the else of a negative check, or after an
// early-return negative check). This ensures that the rewrite from
// s[i+len(sep):] to "after" preserves semantics, since when i == -1,
// s[i+len(sep):] may yield a valid substring (e.g. s[0:] for single-byte
// separators), but "after" would be "".
//
// When len(substr)==1, it's safe to use s[i+1:] even when i < 0.
// Otherwise, each replacement of s[i+1:] must be guarded by a check
// that i is nonnegative.
func checkIdxUses(info *types.Info, uses iter.Seq[inspector.Cursor], s, substr ast.Expr, iObj types.Object) (negative, nonnegative, beforeSlice, afterSlice []ast.Expr) {
requireGuard := true
if l := constSubstrLen(info, substr); l != -1 && l != 1 {
requireGuard = false
}
use := func(cur inspector.Cursor) bool {
ek := cur.ParentEdgeKind()
n := cur.Parent().Node()
switch ek {
case edge.BinaryExpr_X, edge.BinaryExpr_Y:
check := n.(*ast.BinaryExpr)
switch checkIdxComparison(info, check, iObj) {
case -1:
negative = append(negative, check)
return true
case 1:
nonnegative = append(nonnegative, check)
return true
}
// Check is not equivalent to that i < 0 or i >= 0.
// Might be part of an outer slice expression like s[i + k]
// which requires a different check.
// Check that the thing being sliced is s and that the slice
// doesn't have a max index.
if slice, ok := cur.Parent().Parent().Node().(*ast.SliceExpr); ok &&
sameObject(info, s, slice.X) &&
slice.Max == nil {
if isBeforeSlice(info, ek, slice) && (!requireGuard || isSliceIndexGuarded(info, cur, iObj)) {
beforeSlice = append(beforeSlice, slice)
return true
} else if isAfterSlice(info, ek, slice, substr) && (!requireGuard || isSliceIndexGuarded(info, cur, iObj)) {
afterSlice = append(afterSlice, slice)
return true
}
}
case edge.SliceExpr_Low, edge.SliceExpr_High:
slice := n.(*ast.SliceExpr)
// Check that the thing being sliced is s and that the slice doesn't
// have a max index.
if sameObject(info, s, slice.X) && slice.Max == nil {
if isBeforeSlice(info, ek, slice) && (!requireGuard || isSliceIndexGuarded(info, cur, iObj)) {
beforeSlice = append(beforeSlice, slice)
return true
} else if isAfterSlice(info, ek, slice, substr) && (!requireGuard || isSliceIndexGuarded(info, cur, iObj)) {
afterSlice = append(afterSlice, slice)
return true
}
}
}
return false
}
for curIdent := range uses {
if !use(curIdent) {
return nil, nil, nil, nil
}
}
return negative, nonnegative, beforeSlice, afterSlice
}
// hasModifyingUses reports whether any of the uses involve potential
// modifications. Uses involving assignments before the "afterPos" won't be
// considered.
func hasModifyingUses(info *types.Info, uses iter.Seq[inspector.Cursor], afterPos token.Pos) bool {
for curUse := range uses {
ek := curUse.ParentEdgeKind()
if ek == edge.AssignStmt_Lhs {
if curUse.Node().Pos() <= afterPos {
continue
}
assign := curUse.Parent().Node().(*ast.AssignStmt)
if sameObject(info, assign.Lhs[0], curUse.Node().(*ast.Ident)) {
// Modifying use because we are reassigning the value of the object.
return true
}
} else if ek == edge.UnaryExpr_X &&
curUse.Parent().Node().(*ast.UnaryExpr).Op == token.AND {
// Modifying use because we might be passing the object by reference (an explicit &).
// We can ignore the case where we have a method call on the expression (which
// has an implicit &) because we know the type of s and substr are strings
// which cannot have methods on them.
return true
}
}
return false
}
// checkIdxComparison reports whether the check is equivalent to i < 0 or its negation, or neither.
// For equivalent to i >= 0, we only accept this exact BinaryExpr since
// expressions like i > 0 or i >= 1 make a stronger statement about the value of i.
// We avoid suggesting a fix in this case since it may result in an invalid
// transformation (See golang/go#76687).
// Since strings.Index returns exactly -1 if the substring is not found, we
// don't need to handle expressions like i <= -3.
// We return 0 if the expression does not match any of these options.
func checkIdxComparison(info *types.Info, check *ast.BinaryExpr, iObj types.Object) int {
isI := func(e ast.Expr) bool {
id, ok := e.(*ast.Ident)
return ok && info.Uses[id] == iObj
}
if !isI(check.X) && !isI(check.Y) {
return 0
}
// Ensure that the constant (if any) is on the right.
x, op, y := check.X, check.Op, check.Y
if info.Types[x].Value != nil {
x, op, y = y, flip(op), x
}
yIsInt := func(k int64) bool {
return isIntLiteral(info, y, k)
}
if op == token.LSS && yIsInt(0) || // i < 0
op == token.EQL && yIsInt(-1) || // i == -1
op == token.LEQ && yIsInt(-1) { // i <= -1
return -1 // check <=> i is negative
}
if op == token.GEQ && yIsInt(0) || // i >= 0
op == token.NEQ && yIsInt(-1) || // i != -1
op == token.GTR && yIsInt(-1) { // i > -1
return +1 // check <=> i is non-negative
}
return 0 // unknown
}
// flip changes the comparison token as if the operands were flipped.
// It is defined only for == and the four inequalities.
func flip(op token.Token) token.Token {
switch op {
case token.EQL:
return token.EQL // (same)
case token.GEQ:
return token.LEQ
case token.GTR:
return token.LSS
case token.LEQ:
return token.GEQ
case token.LSS:
return token.GTR
}
return op
}
// isBeforeSlice reports whether the SliceExpr is of the form s[:i] or s[0:i].
func isBeforeSlice(info *types.Info, ek edge.Kind, slice *ast.SliceExpr) bool {
return ek == edge.SliceExpr_High && (slice.Low == nil || isZeroIntConst(info, slice.Low))
}
// constSubstrLen returns the constant length of substr, or -1 if unknown.
func constSubstrLen(info *types.Info, substr ast.Expr) int {
// Handle len([]byte(substr))
if call, ok := substr.(*ast.CallExpr); ok {
tv := info.Types[call.Fun]
if tv.IsType() && types.Identical(tv.Type, byteSliceType) {
// Only one arg in []byte conversion.
substr = call.Args[0]
}
}
substrVal := info.Types[substr].Value
if substrVal != nil {
switch substrVal.Kind() {
case constant.String:
return len(constant.StringVal(substrVal))
case constant.Int:
// constant.Value is a byte literal, e.g. bytes.IndexByte(_, 'a')
// or a numeric byte literal, e.g. bytes.IndexByte(_, 65)
// ([]byte(rune) is not legal.)
return 1
}
}
return -1
}
// isAfterSlice reports whether the SliceExpr is of the form s[i+len(substr):],
// or s[i + k:] where k is a const is equal to len(substr).
func isAfterSlice(info *types.Info, ek edge.Kind, slice *ast.SliceExpr, substr ast.Expr) bool {
lowExpr, ok := slice.Low.(*ast.BinaryExpr)
if !ok || slice.High != nil {
return false
}
// Returns true if the expression is a call to len(substr).
isLenCall := func(expr ast.Expr) bool {
call, ok := expr.(*ast.CallExpr)
if !ok || len(call.Args) != 1 {
return false
}
return sameObject(info, substr, call.Args[0]) && typeutil.Callee(info, call) == builtinLen
}
substrLen := constSubstrLen(info, substr)
switch ek {
case edge.BinaryExpr_X:
kVal := info.Types[lowExpr.Y].Value
if kVal == nil {
// i + len(substr)
return lowExpr.Op == token.ADD && isLenCall(lowExpr.Y)
} else {
// i + k
kInt, ok := constant.Int64Val(kVal)
return ok && substrLen == int(kInt)
}
case edge.BinaryExpr_Y:
kVal := info.Types[lowExpr.X].Value
if kVal == nil {
// len(substr) + i
return lowExpr.Op == token.ADD && isLenCall(lowExpr.X)
} else {
// k + i
kInt, ok := constant.Int64Val(kVal)
return ok && substrLen == int(kInt)
}
}
return false
}
// isSliceIndexGuarded reports whether a use of the index variable i (at the given cursor)
// inside a slice expression is dominated by a nonnegative guard.
// A use is considered guarded if any of the following are true:
// - It is inside the Body of an IfStmt whose condition is a nonnegative check on i.
// - It is inside the Else of an IfStmt whose condition is a negative check on i.
// - It is preceded (in the same block) by an IfStmt whose condition is a
// negative check on i with a terminating body (e.g., early return).
//
// Conversely, a use is immediately rejected if:
// - It is inside the Body of an IfStmt whose condition is a negative check on i.
// - It is inside the Else of an IfStmt whose condition is a nonnegative check on i.
//
// We have already checked (see [hasModifyingUses]) that there are no
// intervening uses (incl. via aliases) of i that might alter its value.
func isSliceIndexGuarded(info *types.Info, cur inspector.Cursor, iObj types.Object) bool {
for anc := range cur.Enclosing() {
switch anc.ParentEdgeKind() {
case edge.IfStmt_Body, edge.IfStmt_Else:
ifStmt := anc.Parent().Node().(*ast.IfStmt)
check := condChecksIdx(info, ifStmt.Cond, iObj)
if anc.ParentEdgeKind() == edge.IfStmt_Else {
check = -check
}
if check > 0 {
return true // inside nonnegative-guarded block (i >= 0 here)
}
if check < 0 {
return false // inside negative-guarded block (i < 0 here)
}
case edge.BlockStmt_List:
// Check preceding siblings for early-return negative checks.
for sib, ok := anc.PrevSibling(); ok; sib, ok = sib.PrevSibling() {
ifStmt, ok := sib.Node().(*ast.IfStmt)
if ok && condChecksIdx(info, ifStmt.Cond, iObj) < 0 && bodyTerminates(ifStmt.Body) {
return true // preceded by early-return negative check
}
}
case edge.FuncDecl_Body, edge.FuncLit_Body:
return false // stop at function boundary
}
}
return false
}
// condChecksIdx reports whether cond is a BinaryExpr that checks
// the index variable iObj for negativity or non-negativity.
// Returns -1 for negative (e.g. i < 0), +1 for nonnegative (e.g. i >= 0), 0 otherwise.
func condChecksIdx(info *types.Info, cond ast.Expr, iObj types.Object) int {
binExpr, ok := cond.(*ast.BinaryExpr)
if !ok {
return 0
}
return checkIdxComparison(info, binExpr, iObj)
}
// bodyTerminates reports whether the given block statement unconditionally
// terminates execution (via return, break, continue, or goto).
func bodyTerminates(block *ast.BlockStmt) bool {
if len(block.List) == 0 {
return false
}
last := block.List[len(block.List)-1]
switch last.(type) {
case *ast.ReturnStmt, *ast.BranchStmt:
return true // return, break, continue, goto
}
return false
}
// sameObject reports whether we know that the expressions resolve to the same object.
func sameObject(info *types.Info, expr1, expr2 ast.Expr) bool {
if ident1, ok := expr1.(*ast.Ident); ok {
if ident2, ok := expr2.(*ast.Ident); ok {
uses1, ok1 := info.Uses[ident1]
uses2, ok2 := info.Uses[ident2]
return ok1 && ok2 && uses1 == uses2
}
}
return false
}
================================================
FILE: go/analysis/passes/modernize/stringscutprefix.go
================================================
// Copyright 2025 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package modernize
import (
"fmt"
"go/ast"
"go/token"
"strings"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/types/typeutil"
"golang.org/x/tools/internal/analysis/analyzerutil"
typeindexanalyzer "golang.org/x/tools/internal/analysis/typeindex"
"golang.org/x/tools/internal/astutil"
"golang.org/x/tools/internal/refactor"
"golang.org/x/tools/internal/typesinternal"
"golang.org/x/tools/internal/typesinternal/typeindex"
"golang.org/x/tools/internal/versions"
)
var StringsCutPrefixAnalyzer = &analysis.Analyzer{
Name: "stringscutprefix",
Doc: analyzerutil.MustExtractDoc(doc, "stringscutprefix"),
Requires: []*analysis.Analyzer{
inspect.Analyzer,
typeindexanalyzer.Analyzer,
},
Run: stringscutprefix,
URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/modernize#stringscutprefix",
}
// stringscutprefix offers a fix to replace an if statement which
// calls to the 2 patterns below with strings.CutPrefix or strings.CutSuffix.
//
// Patterns:
//
// 1. if strings.HasPrefix(s, pre) { use(strings.TrimPrefix(s, pre) }
// =>
// if after, ok := strings.CutPrefix(s, pre); ok { use(after) }
//
// 2. if after := strings.TrimPrefix(s, pre); after != s { use(after) }
// =>
// if after, ok := strings.CutPrefix(s, pre); ok { use(after) }
//
// Similar patterns apply for CutSuffix.
//
// The use must occur within the first statement of the block, and the offered fix
// only replaces the first occurrence of strings.TrimPrefix/TrimSuffix.
//
// Variants:
// - bytes.HasPrefix/HasSuffix usage as pattern 1.
func stringscutprefix(pass *analysis.Pass) (any, error) {
var (
index = pass.ResultOf[typeindexanalyzer.Analyzer].(*typeindex.Index)
info = pass.TypesInfo
stringsTrimPrefix = index.Object("strings", "TrimPrefix")
bytesTrimPrefix = index.Object("bytes", "TrimPrefix")
stringsTrimSuffix = index.Object("strings", "TrimSuffix")
bytesTrimSuffix = index.Object("bytes", "TrimSuffix")
)
if !index.Used(stringsTrimPrefix, bytesTrimPrefix, stringsTrimSuffix, bytesTrimSuffix) {
return nil, nil
}
for curFile := range filesUsingGoVersion(pass, versions.Go1_20) {
for curIfStmt := range curFile.Preorder((*ast.IfStmt)(nil)) {
ifStmt := curIfStmt.Node().(*ast.IfStmt)
// pattern1
if call, ok := ifStmt.Cond.(*ast.CallExpr); ok && ifStmt.Init == nil && len(ifStmt.Body.List) > 0 {
obj := typeutil.Callee(info, call)
if !typesinternal.IsFunctionNamed(obj, "strings", "HasPrefix", "HasSuffix") &&
!typesinternal.IsFunctionNamed(obj, "bytes", "HasPrefix", "HasSuffix") {
continue
}
isPrefix := strings.HasSuffix(obj.Name(), "Prefix")
// Replace the first occurrence of strings.TrimPrefix(s, pre) in the first statement only,
// but not later statements in case s or pre are modified by intervening logic (ditto Suffix).
firstStmt := curIfStmt.Child(ifStmt.Body).Child(ifStmt.Body.List[0])
for curCall := range firstStmt.Preorder((*ast.CallExpr)(nil)) {
call1 := curCall.Node().(*ast.CallExpr)
obj1 := typeutil.Callee(info, call1)
// bytesTrimPrefix or stringsTrimPrefix might be nil if the file doesn't import it,
// so we need to ensure the obj1 is not nil otherwise the call1 is not TrimPrefix and cause a panic (ditto Suffix).
if obj1 == nil ||
obj1 != stringsTrimPrefix && obj1 != bytesTrimPrefix &&
obj1 != stringsTrimSuffix && obj1 != bytesTrimSuffix {
continue
}
isPrefix1 := strings.HasSuffix(obj1.Name(), "Prefix")
var cutFuncName, varName, message, fixMessage string
if isPrefix && isPrefix1 {
cutFuncName = "CutPrefix"
varName = "after"
message = "HasPrefix + TrimPrefix can be simplified to CutPrefix"
fixMessage = "Replace HasPrefix/TrimPrefix with CutPrefix"
} else if !isPrefix && !isPrefix1 {
cutFuncName = "CutSuffix"
varName = "before"
message = "HasSuffix + TrimSuffix can be simplified to CutSuffix"
fixMessage = "Replace HasSuffix/TrimSuffix with CutSuffix"
} else {
continue
}
// Have: if strings.HasPrefix(s0, pre0) { ...strings.TrimPrefix(s, pre)... } (ditto Suffix)
var (
s0 = call.Args[0]
pre0 = call.Args[1]
s = call1.Args[0]
pre = call1.Args[1]
)
// check whether the obj1 uses the exact the same argument with strings.HasPrefix
// shadow variables won't be valid because we only access the first statement (ditto Suffix).
if astutil.EqualSyntax(s0, s) && astutil.EqualSyntax(pre0, pre) {
after := refactor.FreshName(info.Scopes[ifStmt], ifStmt.Pos(), varName)
prefix, importEdits := refactor.AddImport(
info,
curFile.Node().(*ast.File),
obj1.Pkg().Name(),
obj1.Pkg().Path(),
cutFuncName,
call.Pos(),
)
okVarName := refactor.FreshName(info.Scopes[ifStmt], ifStmt.Pos(), "ok")
pass.Report(analysis.Diagnostic{
// highlight at HasPrefix call (ditto Suffix).
Pos: call.Pos(),
End: call.End(),
Message: message,
SuggestedFixes: []analysis.SuggestedFix{{
Message: fixMessage,
// if strings.HasPrefix(s, pre) { use(strings.TrimPrefix(s, pre)) }
// ------------ ----------------- ----- --------------------------
// if after, ok := strings.CutPrefix(s, pre); ok { use(after) }
// (ditto Suffix)
TextEdits: append(importEdits, []analysis.TextEdit{
{
Pos: call.Fun.Pos(),
End: call.Fun.Pos(),
NewText: fmt.Appendf(nil, "%s, %s :=", after, okVarName),
},
{
Pos: call.Fun.Pos(),
End: call.Fun.End(),
NewText: fmt.Appendf(nil, "%s%s", prefix, cutFuncName),
},
{
Pos: call.End(),
End: call.End(),
NewText: fmt.Appendf(nil, "; %s ", okVarName),
},
{
Pos: call1.Pos(),
End: call1.End(),
NewText: []byte(after),
},
}...),
}}},
)
break
}
}
}
// pattern2
if bin, ok := ifStmt.Cond.(*ast.BinaryExpr); ok &&
bin.Op == token.NEQ &&
ifStmt.Init != nil &&
isSimpleAssign(ifStmt.Init) {
assign := ifStmt.Init.(*ast.AssignStmt)
if call, ok := assign.Rhs[0].(*ast.CallExpr); ok && assign.Tok == token.DEFINE {
lhs := assign.Lhs[0]
obj := typeutil.Callee(info, call)
if obj == nil ||
obj != stringsTrimPrefix && obj != bytesTrimPrefix && obj != stringsTrimSuffix && obj != bytesTrimSuffix {
continue
}
isPrefix1 := strings.HasSuffix(obj.Name(), "Prefix")
var cutFuncName, message, fixMessage string
if isPrefix1 {
cutFuncName = "CutPrefix"
message = "TrimPrefix can be simplified to CutPrefix"
fixMessage = "Replace TrimPrefix with CutPrefix"
} else {
cutFuncName = "CutSuffix"
message = "TrimSuffix can be simplified to CutSuffix"
fixMessage = "Replace TrimSuffix with CutSuffix"
}
if astutil.EqualSyntax(lhs, bin.X) && astutil.EqualSyntax(call.Args[0], bin.Y) ||
(astutil.EqualSyntax(lhs, bin.Y) && astutil.EqualSyntax(call.Args[0], bin.X)) {
okVarName := freshName(info, index, info.Scopes[ifStmt], ifStmt.Pos(), curIfStmt, curIfStmt, token.NoPos, "ok")
// Have one of:
// if rest := TrimPrefix(s, prefix); rest != s { (ditto Suffix)
// if rest := TrimPrefix(s, prefix); s != rest { (ditto Suffix)
// We use AddImport not to add an import (since it exists already)
// but to compute the correct prefix in the dot-import case.
prefix, importEdits := refactor.AddImport(
info,
curFile.Node().(*ast.File),
obj.Pkg().Name(),
obj.Pkg().Path(),
cutFuncName,
call.Pos(),
)
pass.Report(analysis.Diagnostic{
// highlight from the init and the condition end.
Pos: ifStmt.Init.Pos(),
End: ifStmt.Cond.End(),
Message: message,
SuggestedFixes: []analysis.SuggestedFix{{
Message: fixMessage,
// if x := strings.TrimPrefix(s, pre); x != s ...
// ---- ---------- ------
// if x, ok := strings.CutPrefix (s, pre); ok ...
// (ditto Suffix)
TextEdits: append(importEdits, []analysis.TextEdit{
{
Pos: assign.Lhs[0].End(),
End: assign.Lhs[0].End(),
NewText: fmt.Appendf(nil, ", %s", okVarName),
},
{
Pos: call.Fun.Pos(),
End: call.Fun.End(),
NewText: fmt.Appendf(nil, "%s%s", prefix, cutFuncName),
},
{
Pos: ifStmt.Cond.Pos(),
End: ifStmt.Cond.End(),
NewText: []byte(okVarName),
},
}...),
}},
})
}
}
}
}
}
return nil, nil
}
================================================
FILE: go/analysis/passes/modernize/stringsseq.go
================================================
// Copyright 2025 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package modernize
import (
"fmt"
"go/ast"
"go/token"
"go/types"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/ast/edge"
"golang.org/x/tools/go/types/typeutil"
"golang.org/x/tools/internal/analysis/analyzerutil"
typeindexanalyzer "golang.org/x/tools/internal/analysis/typeindex"
"golang.org/x/tools/internal/typesinternal/typeindex"
"golang.org/x/tools/internal/versions"
)
var StringsSeqAnalyzer = &analysis.Analyzer{
Name: "stringsseq",
Doc: analyzerutil.MustExtractDoc(doc, "stringsseq"),
Requires: []*analysis.Analyzer{
inspect.Analyzer,
typeindexanalyzer.Analyzer,
},
Run: stringsseq,
URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/modernize#stringsseq",
}
// stringsseq offers a fix to replace a call to strings.Split with
// SplitSeq or strings.Fields with FieldsSeq
// when it is the operand of a range loop, either directly:
//
// for _, line := range strings.Split() {...}
//
// or indirectly, if the variable's sole use is the range statement:
//
// lines := strings.Split()
// for _, line := range lines {...}
//
// Variants:
// - bytes.SplitSeq
// - bytes.FieldsSeq
func stringsseq(pass *analysis.Pass) (any, error) {
var (
index = pass.ResultOf[typeindexanalyzer.Analyzer].(*typeindex.Index)
info = pass.TypesInfo
stringsSplit = index.Object("strings", "Split")
stringsFields = index.Object("strings", "Fields")
bytesSplit = index.Object("bytes", "Split")
bytesFields = index.Object("bytes", "Fields")
)
if !index.Used(stringsSplit, stringsFields, bytesSplit, bytesFields) {
return nil, nil
}
for curFile := range filesUsingGoVersion(pass, versions.Go1_24) {
for curRange := range curFile.Preorder((*ast.RangeStmt)(nil)) {
rng := curRange.Node().(*ast.RangeStmt)
// Reject "for i, line := ..." since SplitSeq is not an iter.Seq2.
// (We require that i is blank.)
if id, ok := rng.Key.(*ast.Ident); ok && id.Name != "_" {
continue
}
// Find the call operand of the range statement,
// whether direct or indirect.
call, ok := rng.X.(*ast.CallExpr)
if !ok {
if id, ok := rng.X.(*ast.Ident); ok {
if v, ok := info.Uses[id].(*types.Var); ok {
if ek, idx := curRange.ParentEdge(); ek == edge.BlockStmt_List && idx > 0 {
curPrev, _ := curRange.PrevSibling()
if assign, ok := curPrev.Node().(*ast.AssignStmt); ok &&
assign.Tok == token.DEFINE &&
len(assign.Lhs) == 1 &&
len(assign.Rhs) == 1 &&
info.Defs[assign.Lhs[0].(*ast.Ident)] == v &&
soleUseIs(index, v, id) {
// Have:
// lines := ...
// for _, line := range lines {...}
// and no other uses of lines.
call, _ = assign.Rhs[0].(*ast.CallExpr)
}
}
}
}
}
if call != nil {
var edits []analysis.TextEdit
if rng.Key != nil {
// Delete (blank) RangeStmt.Key:
// for _, line := -> for line :=
// for _, _ := -> for
// for _ := -> for
end := rng.Range
if rng.Value != nil {
end = rng.Value.Pos()
}
edits = append(edits, analysis.TextEdit{
Pos: rng.Key.Pos(),
End: end,
})
}
sel, ok := call.Fun.(*ast.SelectorExpr)
if !ok {
continue
}
switch obj := typeutil.Callee(info, call); obj {
case stringsSplit, stringsFields, bytesSplit, bytesFields:
oldFnName := obj.Name()
seqFnName := fmt.Sprintf("%sSeq", oldFnName)
pass.Report(analysis.Diagnostic{
Pos: sel.Pos(),
End: sel.End(),
Message: fmt.Sprintf("Ranging over %s is more efficient", seqFnName),
SuggestedFixes: []analysis.SuggestedFix{{
Message: fmt.Sprintf("Replace %s with %s", oldFnName, seqFnName),
TextEdits: append(edits, analysis.TextEdit{
Pos: sel.Sel.Pos(),
End: sel.Sel.End(),
NewText: []byte(seqFnName)}),
}},
})
}
}
}
}
return nil, nil
}
================================================
FILE: go/analysis/passes/modernize/testdata/src/any/any.go
================================================
package any
func _(x interface{}) {} // want "interface{} can be replaced by any"
func _() {
var x interface{} // want "interface{} can be replaced by any"
const any = 1
var y interface{} // nope: any is shadowed here
_, _ = x, y
}
================================================
FILE: go/analysis/passes/modernize/testdata/src/any/any.go.golden
================================================
package any
func _(x any) {} // want "interface{} can be replaced by any"
func _() {
var x any // want "interface{} can be replaced by any"
const any = 1
var y interface{} // nope: any is shadowed here
_, _ = x, y
}
================================================
FILE: go/analysis/passes/modernize/testdata/src/any/generated.go
================================================
// Code generated by hand. DO NOT EDIT.
package any
func _(x interface{}) {} // want "interface{} can be replaced by any"
func _() {
var x interface{} // want "interface{} can be replaced by any"
const any = 1
var y interface{} // nope: any is shadowed here
_, _ = x, y
}
================================================
FILE: go/analysis/passes/modernize/testdata/src/any/generated.go.golden
================================================
// Code generated by hand. DO NOT EDIT.
package any
func _(x interface{}) {} // want "interface{} can be replaced by any"
func _() {
var x interface{} // want "interface{} can be replaced by any"
const any = 1
var y interface{} // nope: any is shadowed here
_, _ = x, y
}
================================================
FILE: go/analysis/passes/modernize/testdata/src/appendclipped/appendclipped.go
================================================
package appendclipped
import (
"os"
"slices"
)
type (
Bytes []byte
Bytes2 []byte
)
func _(s, other []string) {
print(append([]string{}, s...)) // want "Replace append with slices.Clone"
print(append([]string(nil), s...)) // want "Replace append with slices.Clone"
print(append(Bytes(nil), Bytes{1, 2, 3}...)) // want "Replace append with slices.Clone"
print(append(other[:0:0], s...)) // want "Replace append with slices.Clone"
print(append(other[:0:0], os.Environ()...)) // want "Redundant clone of os.Environ()"
print(append(other[:0], s...)) // nope: intent may be to mutate other
print(append(append(append([]string{}, s...), other...), other...)) // want "Replace append with slices.Concat"
print(append(append(append([]string(nil), s...), other...), other...)) // want "Replace append with slices.Concat"
print(append(append(Bytes(nil), Bytes{1, 2, 3}...), Bytes{4, 5, 6}...)) // want "Replace append with slices.Concat"
print(append(append(append(other[:0:0], s...), other...), other...)) // want "Replace append with slices.Concat"
print(append(append(append(other[:0:0], os.Environ()...), other...), other...)) // want "Replace append with slices.Concat"
print(append(append(other[:len(other):len(other)], s...), other...)) // want "Replace append with slices.Concat"
print(append(append(slices.Clip(other), s...), other...)) // want "Replace append with slices.Concat"
print(append(append(append(other[:0], s...), other...), other...)) // nope: intent may be to mutate other
}
var (
_ Bytes = append(Bytes(nil), []byte(nil)...) // nope: correct fix requires Clone[Bytes] (#73661)
_ Bytes = append([]byte(nil), Bytes(nil)...) // nope: correct fix requires Clone[Bytes] (#73661)
_ Bytes2 = append([]byte(nil), Bytes(nil)...) // nope: correct fix requires Clone[Bytes2] (#73661)
)
================================================
FILE: go/analysis/passes/modernize/testdata/src/appendclipped/appendclipped.go.golden
================================================
package appendclipped
import (
"os"
"slices"
)
type (
Bytes []byte
Bytes2 []byte
)
func _(s, other []string) {
print(slices.Clone(s)) // want "Replace append with slices.Clone"
print(slices.Clone(s)) // want "Replace append with slices.Clone"
print(slices.Clone(Bytes{1, 2, 3})) // want "Replace append with slices.Clone"
print(slices.Clone(s)) // want "Replace append with slices.Clone"
print(os.Environ()) // want "Redundant clone of os.Environ()"
print(append(other[:0], s...)) // nope: intent may be to mutate other
print(slices.Concat(s, other, other)) // want "Replace append with slices.Concat"
print(slices.Concat(s, other, other)) // want "Replace append with slices.Concat"
print(slices.Concat(Bytes{1, 2, 3}, Bytes{4, 5, 6})) // want "Replace append with slices.Concat"
print(slices.Concat(s, other, other)) // want "Replace append with slices.Concat"
print(slices.Concat(os.Environ(), other, other)) // want "Replace append with slices.Concat"
print(slices.Concat(other, s, other)) // want "Replace append with slices.Concat"
print(slices.Concat(other, s, other)) // want "Replace append with slices.Concat"
print(append(append(append(other[:0], s...), other...), other...)) // nope: intent may be to mutate other
}
var (
_ Bytes = append(Bytes(nil), []byte(nil)...) // nope: correct fix requires Clone[Bytes] (#73661)
_ Bytes = append([]byte(nil), Bytes(nil)...) // nope: correct fix requires Clone[Bytes] (#73661)
_ Bytes2 = append([]byte(nil), Bytes(nil)...) // nope: correct fix requires Clone[Bytes2] (#73661)
)
================================================
FILE: go/analysis/passes/modernize/testdata/src/appendclipped/bytesclone.go
================================================
package appendclipped
import (
"bytes"
)
var _ bytes.Buffer
func _(b []byte) {
print(append([]byte{}, b...)) // want "Replace append with bytes.Clone"
}
================================================
FILE: go/analysis/passes/modernize/testdata/src/appendclipped/bytesclone.go.golden
================================================
package appendclipped
import (
"bytes"
)
var _ bytes.Buffer
func _(b []byte) {
print(bytes.Clone(b)) // want "Replace append with bytes.Clone"
}
================================================
FILE: go/analysis/passes/modernize/testdata/src/atomictypes/atomic.go
================================================
package atomic
import (
"log"
"sync/atomic"
)
type X struct {
x int32 // want "var x int32 may be simplified using atomic.Int32"
}
type Z struct {
y int64 // want "var y int64 may be simplified using atomic.Int64"
z int64
}
func (wrapper *Z) fix() {
var x int32 // want "var x int32 may be simplified using atomic.Int32"
for range 100 {
go atomic.AddInt32(&x, 1)
}
var x2 int32 = 5 // nope: can't assign an int to an atomic.Int32
for range 100 {
go atomic.AddInt32(&x2, 1)
}
var y X
for range 100 {
go atomic.CompareAndSwapInt32(&y.x, 2, 3)
}
atomic.CompareAndSwapInt64(&wrapper.y, 2, 3)
var z int32
_ = z
if z == 0 { // nope: cannot rewrite rvalue use (unsynchronized load)
go atomic.LoadInt32(&z)
log.Print(z)
}
}
type Y int32
func (y Y) dontfix(x int32) (result int32) {
atomic.AddInt32(&x, 1) // nope - v is a type param
atomic.StoreInt32(&result, 100) // nope - v is a return value
atomic.AddInt32((*int32)(&y), 1) // nope - v is a receiver var
w := Z{
z: 1,
}
atomic.AddInt64(&w.z, 1) // nope - cannot fix initial value assignment
return
}
================================================
FILE: go/analysis/passes/modernize/testdata/src/atomictypes/atomic.go.golden
================================================
package atomic
import (
"log"
"sync/atomic"
)
type X struct {
x atomic.Int32 // want "var x int32 may be simplified using atomic.Int32"
}
type Z struct {
y atomic.Int64 // want "var y int64 may be simplified using atomic.Int64"
z int64
}
func (wrapper *Z) fix() {
var x atomic.Int32 // want "var x int32 may be simplified using atomic.Int32"
for range 100 {
go x.Add(1)
}
var x2 int32 = 5 // nope: can't assign an int to an atomic.Int32
for range 100 {
go atomic.AddInt32(&x2, 1)
}
var y X
for range 100 {
go y.x.CompareAndSwap(2, 3)
}
wrapper.y.CompareAndSwap(2, 3)
var z int32
_ = z
if z == 0 { // nope: cannot rewrite rvalue use (unsynchronized load)
go atomic.LoadInt32(&z)
log.Print(z)
}
}
type Y int32
func (y Y) dontfix(x int32) (result int32) {
atomic.AddInt32(&x, 1) // nope - v is a type param
atomic.StoreInt32(&result, 100) // nope - v is a return value
atomic.AddInt32((*int32)(&y), 1) // nope - v is a receiver var
w := Z{
z: 1,
}
atomic.AddInt64(&w.z, 1) // nope - cannot fix initial value assignment
return
}
================================================
FILE: go/analysis/passes/modernize/testdata/src/atomictypes/atomic_shadow.go
================================================
package atomic
import myatomic "sync/atomic"
func _() {
var x int32 // want "var x int32 may be simplified using atomic.Int32"
for range 100 {
go myatomic.AddInt32(&x, 1)
}
}
================================================
FILE: go/analysis/passes/modernize/testdata/src/atomictypes/atomic_shadow.go.golden
================================================
package atomic
import myatomic "sync/atomic"
func _() {
var x myatomic.Int32 // want "var x int32 may be simplified using atomic.Int32"
for range 100 {
go x.Add(1)
}
}
================================================
FILE: go/analysis/passes/modernize/testdata/src/atomictypes/go118/atomic_go118.go
================================================
//go:build !go1.19
package go118
import "sync/atomic"
func _() {
var x int32 // AddInt32 not available until go1.19
for range 100 {
go atomic.AddInt32(&x, 1)
}
}
================================================
FILE: go/analysis/passes/modernize/testdata/src/atomictypes/go118/atomic_go118.go.golden
================================================
//go:build !go1.19
package go118
import "sync/atomic"
func _() {
var x int32 // AddInt32 not available until go1.19
for range 100 {
go atomic.AddInt32(&x, 2)
}
}
================================================
FILE: go/analysis/passes/modernize/testdata/src/atomictypes/go120/atomic_go120.go
================================================
//go:build !go1.21
package go120
import "sync/atomic"
func _() {
var x int32 // AndInt32 not available until go1.23
for range 100 {
go atomic.AndInt32(&x, 1)
}
}
================================================
FILE: go/analysis/passes/modernize/testdata/src/atomictypes/go120/atomic_go120.go.golden
================================================
//go:build !go1.21
package go120
import "sync/atomic"
func _() {
var x int32 // AndInt32 not available until go1.23
for range 100 {
go atomic.AndInt32(&x, 1)
}
}
================================================
FILE: go/analysis/passes/modernize/testdata/src/atomictypes/ignored/atomic.go
================================================
package ignored
import (
"sync/atomic"
)
var x int32 // don't fix - package has ignored files
func _() {
for range 100 {
go atomic.AddInt32(&x, 1)
}
}
================================================
FILE: go/analysis/passes/modernize/testdata/src/atomictypes/ignored/atomic.go.golden
================================================
package ignored
import (
"sync/atomic"
)
var x int32 // don't fix - package has ignored files
func _() {
for range 100 {
go atomic.AddInt32(&x, 1)
}
}
================================================
FILE: go/analysis/passes/modernize/testdata/src/atomictypes/ignored/atomic_ignored.go
================================================
//go:build !go1.19
package ignored
import "sync/atomic"
func _() {
for range 100 {
go atomic.AddInt32(&x, 1)
}
}
================================================
FILE: go/analysis/passes/modernize/testdata/src/bloop/bloop.go
================================================
package bloop
================================================
FILE: go/analysis/passes/modernize/testdata/src/bloop/bloop_test.go
================================================
//go:build go1.24
package bloop
import (
"sync"
"testing"
)
func BenchmarkA(b *testing.B) {
println("slow")
b.ResetTimer()
for range b.N { // want "b.N can be modernized using b.Loop.."
}
}
func BenchmarkB(b *testing.B) {
// setup
{
b.StopTimer()
println("slow")
b.StartTimer()
}
for i := range b.N { // Nope. Should we change this to "for i := 0; b.Loop(); i++"?
print(i)
}
b.StopTimer()
println("slow")
}
func BenchmarkC(b *testing.B) {
// setup
{
b.StopTimer()
println("slow")
b.StartTimer()
}
for i := 0; i < b.N; i++ { // want "b.N can be modernized using b.Loop.."
println("no uses of i")
}
b.StopTimer()
println("slow")
}
func BenchmarkD(b *testing.B) {
for i := 0; i < b.N; i++ { // want "b.N can be modernized using b.Loop.."
println(i)
}
}
func BenchmarkE(b *testing.B) {
b.Run("sub", func(b *testing.B) {
b.StopTimer() // not deleted
println("slow")
b.StartTimer() // not deleted
// ...
})
b.ResetTimer()
for i := 0; i < b.N; i++ { // want "b.N can be modernized using b.Loop.."
println("no uses of i")
}
b.StopTimer()
println("slow")
}
func BenchmarkF(b *testing.B) {
var wg sync.WaitGroup
wg.Add(1)
go func() {
defer wg.Done()
for i := 0; i < b.N; i++ { // nope: b.N accessed from a FuncLit
}
}()
wg.Wait()
}
func BenchmarkG(b *testing.B) {
var wg sync.WaitGroup
poster := func() {
for i := 0; i < b.N; i++ { // nope: b.N accessed from a FuncLit
}
wg.Done()
}
wg.Add(2)
for i := 0; i < 2; i++ {
go poster()
}
wg.Wait()
}
func BenchmarkH(b *testing.B) {
var wg sync.WaitGroup
wg.Add(1)
go func() {
defer wg.Done()
for range b.N { // nope: b.N accessed from a FuncLit
}
}()
wg.Wait()
}
func BenchmarkI(b *testing.B) {
for i := 0; i < b.N; i++ { // nope: b.N accessed more than once in benchmark
}
for i := 0; i < b.N; i++ { // nope: b.N accessed more than once in benchmark
}
}
func BenchmarkJ(b *testing.B) {
var wg sync.WaitGroup
ch := make(chan int, 10)
wg.Add(1)
go func() {
for i := 0; i < b.N; i++ {
<-ch
}
wg.Done()
}()
b.ResetTimer()
for i := 0; i < b.N; i++ { // nope: multiple b.N loops in same function
ch <- i
}
b.StopTimer()
wg.Wait()
}
================================================
FILE: go/analysis/passes/modernize/testdata/src/bloop/bloop_test.go.golden
================================================
//go:build go1.24
package bloop
import (
"sync"
"testing"
)
func BenchmarkA(b *testing.B) {
println("slow")
for b.Loop() { // want "b.N can be modernized using b.Loop.."
}
}
func BenchmarkB(b *testing.B) {
// setup
{
b.StopTimer()
println("slow")
b.StartTimer()
}
for i := range b.N { // Nope. Should we change this to "for i := 0; b.Loop(); i++"?
print(i)
}
b.StopTimer()
println("slow")
}
func BenchmarkC(b *testing.B) {
// setup
{
println("slow")
}
for b.Loop() { // want "b.N can be modernized using b.Loop.."
println("no uses of i")
}
b.StopTimer()
println("slow")
}
func BenchmarkD(b *testing.B) {
for i := 0; b.Loop(); i++ { // want "b.N can be modernized using b.Loop.."
println(i)
}
}
func BenchmarkE(b *testing.B) {
b.Run("sub", func(b *testing.B) {
b.StopTimer() // not deleted
println("slow")
b.StartTimer() // not deleted
// ...
})
for b.Loop() { // want "b.N can be modernized using b.Loop.."
println("no uses of i")
}
b.StopTimer()
println("slow")
}
func BenchmarkF(b *testing.B) {
var wg sync.WaitGroup
wg.Add(1)
go func() {
defer wg.Done()
for i := 0; i < b.N; i++ { // nope: b.N accessed from a FuncLit
}
}()
wg.Wait()
}
func BenchmarkG(b *testing.B) {
var wg sync.WaitGroup
poster := func() {
for i := 0; i < b.N; i++ { // nope: b.N accessed from a FuncLit
}
wg.Done()
}
wg.Add(2)
for i := 0; i < 2; i++ {
go poster()
}
wg.Wait()
}
func BenchmarkH(b *testing.B) {
var wg sync.WaitGroup
wg.Add(1)
go func() {
defer wg.Done()
for range b.N { // nope: b.N accessed from a FuncLit
}
}()
wg.Wait()
}
func BenchmarkI(b *testing.B) {
for i := 0; i < b.N; i++ { // nope: b.N accessed more than once in benchmark
}
for i := 0; i < b.N; i++ { // nope: b.N accessed more than once in benchmark
}
}
func BenchmarkJ(b *testing.B) {
var wg sync.WaitGroup
ch := make(chan int, 10)
wg.Add(1)
go func() {
for i := 0; i < b.N; i++ {
<-ch
}
wg.Done()
}()
b.ResetTimer()
for i := 0; i < b.N; i++ { // nope: multiple b.N loops in same function
ch <- i
}
b.StopTimer()
wg.Wait()
}
================================================
FILE: go/analysis/passes/modernize/testdata/src/errorsastype/dotimport/a.go
================================================
package errorsastype
import (
. "errors"
"os"
)
func _(err error) {
var patherr *os.PathError
if As(err, &patherr) { // want `errors.As can be simplified using AsType\[\*os.PathError\]`
print(patherr)
}
}
================================================
FILE: go/analysis/passes/modernize/testdata/src/errorsastype/dotimport/a.go.golden
================================================
package errorsastype
import (
. "errors"
"os"
)
func _(err error) {
if patherr, ok := AsType[*os.PathError](err); ok { // want `errors.As can be simplified using AsType\[\*os.PathError\]`
print(patherr)
}
}
================================================
FILE: go/analysis/passes/modernize/testdata/src/errorsastype/errorsastype.go
================================================
package errorsastype
import (
"errors"
"os"
)
func _(err error) {
{
var patherr *os.PathError
if errors.As(err, &patherr) { // want `errors.As can be simplified using AsType\[\*os.PathError\]`
print(patherr)
}
}
{
var patherr *os.PathError
print("not a use of patherr")
if errors.As(err, &patherr) { // want `errors.As can be simplified using AsType\[\*os.PathError\]`
print(patherr)
}
print("also not a use of patherr")
}
{
var patherr *os.PathError
print(patherr)
if errors.As(err, &patherr) { // nope: patherr is used outside scope of if
print(patherr)
}
}
{
var patherr *os.PathError
if errors.As(err, &patherr) { // nope: patherr is used outside scope of if
print(patherr)
}
print(patherr)
}
// Test of 'ok' var shadowing/freshness.
const ok = 1
{
var patherr *os.PathError
if errors.As(err, &patherr) { // want `errors.As can be simplified using AsType\[\*os.PathError\]`
print(patherr)
}
}
{
var patherr *os.PathError
if errors.As(err, &patherr) { // want `errors.As can be simplified using AsType\[\*os.PathError\]`
print(patherr, ok)
}
}
}
================================================
FILE: go/analysis/passes/modernize/testdata/src/errorsastype/errorsastype.go.golden
================================================
package errorsastype
import (
"errors"
"os"
)
func _(err error) {
{
if patherr, ok := errors.AsType[*os.PathError](err); ok { // want `errors.As can be simplified using AsType\[\*os.PathError\]`
print(patherr)
}
}
{
print("not a use of patherr")
if patherr, ok := errors.AsType[*os.PathError](err); ok { // want `errors.As can be simplified using AsType\[\*os.PathError\]`
print(patherr)
}
print("also not a use of patherr")
}
{
var patherr *os.PathError
print(patherr)
if errors.As(err, &patherr) { // nope: patherr is used outside scope of if
print(patherr)
}
}
{
var patherr *os.PathError
if errors.As(err, &patherr) { // nope: patherr is used outside scope of if
print(patherr)
}
print(patherr)
}
// Test of 'ok' var shadowing/freshness.
const ok = 1
{
if patherr, ok := errors.AsType[*os.PathError](err); ok { // want `errors.As can be simplified using AsType\[\*os.PathError\]`
print(patherr)
}
}
{
if patherr, ok0 := errors.AsType[*os.PathError](err); ok0 { // want `errors.As can be simplified using AsType\[\*os.PathError\]`
print(patherr, ok)
}
}
}
================================================
FILE: go/analysis/passes/modernize/testdata/src/fieldsseq/fieldsseq.go
================================================
//go:build go1.24
package fieldsseq
import (
"bytes"
"strings"
)
func _() {
for _, line := range strings.Fields("") { // want "Ranging over FieldsSeq is more efficient"
println(line)
}
for i, line := range strings.Fields("") { // nope: uses index var
println(i, line)
}
for i, _ := range strings.Fields("") { // nope: uses index var
println(i)
}
for i := range strings.Fields("") { // nope: uses index var
println(i)
}
for _ = range strings.Fields("") { // want "Ranging over FieldsSeq is more efficient"
}
for range strings.Fields("") { // want "Ranging over FieldsSeq is more efficient"
}
for range bytes.Fields(nil) { // want "Ranging over FieldsSeq is more efficient"
}
{
lines := strings.Fields("") // want "Ranging over FieldsSeq is more efficient"
for _, line := range lines {
println(line)
}
}
{
lines := strings.Fields("") // nope: lines is used not just by range
for _, line := range lines {
println(line)
}
println(lines)
}
}
================================================
FILE: go/analysis/passes/modernize/testdata/src/fieldsseq/fieldsseq.go.golden
================================================
//go:build go1.24
package fieldsseq
import (
"bytes"
"strings"
)
func _() {
for line := range strings.FieldsSeq("") { // want "Ranging over FieldsSeq is more efficient"
println(line)
}
for i, line := range strings.Fields( "") { // nope: uses index var
println(i, line)
}
for i, _ := range strings.Fields( "") { // nope: uses index var
println(i)
}
for i := range strings.Fields( "") { // nope: uses index var
println(i)
}
for range strings.FieldsSeq("") { // want "Ranging over FieldsSeq is more efficient"
}
for range strings.FieldsSeq("") { // want "Ranging over FieldsSeq is more efficient"
}
for range bytes.FieldsSeq(nil) { // want "Ranging over FieldsSeq is more efficient"
}
{
lines := strings.FieldsSeq("") // want "Ranging over FieldsSeq is more efficient"
for line := range lines {
println(line)
}
}
{
lines := strings.Fields( "") // nope: lines is used not just by range
for _, line := range lines {
println(line)
}
println(lines)
}
}
================================================
FILE: go/analysis/passes/modernize/testdata/src/fieldsseq/fieldsseq_go123.go
================================================
package fieldsseq
================================================
FILE: go/analysis/passes/modernize/testdata/src/fmtappendf/fmtappendf.go
================================================
package fmtappendf
import (
"fmt"
)
func two() string {
return "two"
}
func bye() {
_ = []byte(fmt.Sprintf("bye %d", 1)) // want "Replace .*Sprintf.* with fmt.Appendf"
}
func funcsandvars() {
one := "one"
_ = []byte(fmt.Sprintf("bye %d %s %s", 1, two(), one)) // want "Replace .*Sprintf.* with fmt.Appendf"
}
func typealias() {
type b = byte
type bt = []byte
_ = []b(fmt.Sprintf("bye %d", 1)) // want "Replace .*Sprintf.* with fmt.Appendf"
_ = bt(fmt.Sprintf("bye %d", 1)) // want "Replace .*Sprintf.* with fmt.Appendf"
}
func otherprints() {
_ = []byte(fmt.Sprint("bye %d", 1)) // want "Replace .*Sprint.* with fmt.Append"
_ = []byte(fmt.Sprintln("bye %d", 1)) // want "Replace .*Sprintln.* with fmt.Appendln"
}
func comma() {
type S struct{ Bytes []byte }
var _ = struct{ A S }{
A: S{
Bytes: []byte( // want "Replace .*Sprint.* with fmt.Appendf"
fmt.Sprintf("%d", 0),
),
},
}
_ = []byte( // want "Replace .*Sprint.* with fmt.Appendf"
fmt.Sprintf("%d", 0),
)
}
func emptystring() {
// empty string edge case only applies to Sprintf
_ = []byte(fmt.Sprintln("")) // want "Replace .*Sprintln.* with fmt.Appendln"
// nope - these return []byte{}, while the fmt.Append version returns nil
_ = []byte(fmt.Sprint(""))
_ = []byte(fmt.Sprintf("%s", ""))
_ = []byte(fmt.Sprintf("%#s", ""))
_ = []byte(fmt.Sprintf("%s%v", "", getString()))
// conservatively omitting a suggested fix (ignoring precision and args)
_ = []byte(fmt.Sprintf("%.0q", "notprinted"))
_ = []byte(fmt.Sprintf("%v", "nonempty"))
// has non-operation characters
_ = []byte(fmt.Sprintf("%vother", "")) // want "Replace .*Sprint.* with fmt.Appendf"
}
func multiline() []byte {
_ = []byte( // want "Replace .*Sprintf.* with fmt.Appendf"
fmt.Sprintf("str %d", 1))
return []byte( // want "Replace .*Sprintf.* with fmt.Appendf"
fmt.Sprintf("str %d", 1),
)
}
func getString() string {
return ""
}
================================================
FILE: go/analysis/passes/modernize/testdata/src/fmtappendf/fmtappendf.go.golden
================================================
package fmtappendf
import (
"fmt"
)
func two() string {
return "two"
}
func bye() {
_ = fmt.Appendf(nil, "bye %d", 1) // want "Replace .*Sprintf.* with fmt.Appendf"
}
func funcsandvars() {
one := "one"
_ = fmt.Appendf(nil, "bye %d %s %s", 1, two(), one) // want "Replace .*Sprintf.* with fmt.Appendf"
}
func typealias() {
type b = byte
type bt = []byte
_ = fmt.Appendf(nil, "bye %d", 1) // want "Replace .*Sprintf.* with fmt.Appendf"
_ = fmt.Appendf(nil, "bye %d", 1) // want "Replace .*Sprintf.* with fmt.Appendf"
}
func otherprints() {
_ = fmt.Append(nil, "bye %d", 1) // want "Replace .*Sprint.* with fmt.Append"
_ = fmt.Appendln(nil, "bye %d", 1) // want "Replace .*Sprintln.* with fmt.Appendln"
}
func comma() {
type S struct{ Bytes []byte }
var _ = struct{ A S }{
A: S{
Bytes: fmt.Appendf(nil, "%d", 0),
},
}
_ = fmt.Appendf(nil, "%d", 0)
}
func emptystring() {
// empty string edge case only applies to Sprintf
_ = fmt.Appendln(nil, "") // want "Replace .*Sprintln.* with fmt.Appendln"
// nope - these return []byte{}, while the fmt.Append version returns nil
_ = []byte(fmt.Sprint(""))
_ = []byte(fmt.Sprintf("%s", ""))
_ = []byte(fmt.Sprintf("%#s", ""))
_ = []byte(fmt.Sprintf("%s%v", "", getString()))
// conservatively omitting a suggested fix (ignoring precision and args)
_ = []byte(fmt.Sprintf("%.0q", "notprinted"))
_ = []byte(fmt.Sprintf("%v", "nonempty"))
// has non-operation characters
_ = fmt.Appendf(nil, "%vother", "") // want "Replace .*Sprint.* with fmt.Appendf"
}
func multiline() []byte {
_ = fmt.Appendf(nil, "str %d", 1)
return fmt.Appendf(nil, "str %d", 1)
}
func getString() string {
return ""
}
================================================
FILE: go/analysis/passes/modernize/testdata/src/forvar/forvar.go
================================================
package forvar
func _(m map[int]int, s []int) {
// changed
for i := range s {
i := i // want "copying variable is unneeded"
go f(i)
}
for _, v := range s {
v := v // want "copying variable is unneeded"
go f(v)
}
for k, v := range m {
k := k // want "copying variable is unneeded"
v := v // want "copying variable is unneeded"
go f(k)
go f(v)
}
for k, v := range m {
v := v // want "copying variable is unneeded"
k := k // want "copying variable is unneeded"
go f(k)
go f(v)
}
for k, v := range m {
k, v := k, v // want "copying variable is unneeded"
go f(k)
go f(v)
}
for k, v := range m {
v, k := v, k // want "copying variable is unneeded"
go f(k)
go f(v)
}
for i := range s {
/* hi */ i := i // want "copying variable is unneeded"
go f(i)
}
for v := range m {
if v := v; true { // want "copying variable is unneeded"
print(v)
}
}
// nope
var i, k, v int
for i = range s { // nope, scope change
i := i
go f(i)
}
for _, v = range s { // nope, scope change
v := v
go f(v)
}
for k = range m { // nope, scope change
k := k
go f(k)
}
for k, v = range m { // nope, scope change
k := k
v := v
go f(k)
go f(v)
}
for _, v = range m { // nope, scope change
v := v
go f(v)
}
for _, v = range m { // nope, not x := x
v := i
go f(v)
}
for k, v := range m { // nope, LHS and RHS differ
v, k := k, v
go f(k)
go f(v)
}
for k, v := range m { // nope, not a simple redecl
k, v, x := k, v, 1
go f(k)
go f(v)
go f(x)
}
for i := range s { // nope, not a simple redecl
i := (i)
go f(i)
}
for i := range s { // nope, not a simple redecl
i := i + 1
go f(i)
}
for v := range m {
if v := v; true { // nope, would merge distinct outer and inner variables v
print(v)
}
print(v)
}
}
func f(n int) {}
================================================
FILE: go/analysis/passes/modernize/testdata/src/forvar/forvar.go.golden
================================================
package forvar
func _(m map[int]int, s []int) {
// changed
for i := range s {
go f(i)
}
for _, v := range s {
go f(v)
}
for k, v := range m {
go f(k)
go f(v)
}
for k, v := range m {
go f(k)
go f(v)
}
for k, v := range m {
go f(k)
go f(v)
}
for k, v := range m {
go f(k)
go f(v)
}
for i := range s {
go f(i)
}
for v := range m {
if true { // want "copying variable is unneeded"
print(v)
}
}
// nope
var i, k, v int
for i = range s { // nope, scope change
i := i
go f(i)
}
for _, v = range s { // nope, scope change
v := v
go f(v)
}
for k = range m { // nope, scope change
k := k
go f(k)
}
for k, v = range m { // nope, scope change
k := k
v := v
go f(k)
go f(v)
}
for _, v = range m { // nope, scope change
v := v
go f(v)
}
for _, v = range m { // nope, not x := x
v := i
go f(v)
}
for k, v := range m { // nope, LHS and RHS differ
v, k := k, v
go f(k)
go f(v)
}
for k, v := range m { // nope, not a simple redecl
k, v, x := k, v, 1
go f(k)
go f(v)
go f(x)
}
for i := range s { // nope, not a simple redecl
i := (i)
go f(i)
}
for i := range s { // nope, not a simple redecl
i := i + 1
go f(i)
}
for v := range m {
if v := v; true { // nope, would merge distinct outer and inner variables v
print(v)
}
print(v)
}
}
func f(n int) {}
================================================
FILE: go/analysis/passes/modernize/testdata/src/mapsloop/mapsloop.go
================================================
//go:build go1.23
package mapsloop
import (
"iter"
"maps"
)
var _ = maps.Clone[M] // force "maps" import so that each diagnostic doesn't add one
type M map[int]string
// -- src is map --
func useCopy(dst, src map[int]string) {
// Replace loop by maps.Copy.
for key, value := range src {
// A
dst[key] = value // want "Replace m\\[k\\]=v loop with maps.Copy"
}
}
func useCopyRetrieveMap(x map[int]int) {
getMap := func(int) map[int]int { return nil }
for i, v := range x {
// TODO(yuchen): don't assume that getMap returns the same map each time and has no effects.
//
// So, to avoid changing the cardinality of side effects,
// the limit expression must not involve function calls (e.g. seq.Len()) or channel receives.
getMap(0)[i] = v // want "Replace m\\[k\\]=v loop with maps.Copy"
}
}
func useCopyGeneric[K comparable, V any, M ~map[K]V](dst, src M) {
// Replace loop by maps.Copy.
for key, value := range src {
// A
dst[key] = value // want "Replace m\\[k\\]=v loop with maps.Copy"
}
}
func useCopyNotClone(src map[int]string) {
// Clone is tempting but wrong when src may be nil; see #71844.
// Replace make(...) by maps.Copy.
dst := make(map[int]string, len(src))
// A
for key, value := range src {
// B
dst[key] = value // want "Replace m\\[k\\]=v loop with maps.Copy"
// C
}
// A
dst = map[int]string{}
// B
for key, value := range src {
// C
dst[key] = value // want "Replace m\\[k\\]=v loop with maps.Copy"
}
println(dst)
}
func useCopyParen(src map[int]string) {
// Clone is tempting but wrong when src may be nil; see #71844.
// Replace (make)(...) by maps.Clone.
dst := (make)(map[int]string, len(src))
for key, value := range src {
dst[key] = value // want "Replace m\\[k\\]=v loop with maps.Copy"
}
dst = (map[int]string{})
for key, value := range src {
dst[key] = value // want "Replace m\\[k\\]=v loop with maps.Copy"
}
println(dst)
}
func useCopy_typesDiffer(src M) {
// Replace loop but not make(...) as maps.Copy(src) would return wrong type M.
dst := make(map[int]string, len(src))
for key, value := range src {
dst[key] = value // want "Replace m\\[k\\]=v loop with maps.Copy"
}
println(dst)
}
func useCopy_typesDiffer2(src map[int]string) {
// Replace loop but not make(...) as maps.Copy(src) would return wrong type map[int]string.
dst := make(M, len(src))
for key, value := range src {
dst[key] = value // want "Replace m\\[k\\]=v loop with maps.Copy"
}
println(dst)
}
func useClone_typesDiffer3(src map[int]string) {
// Clone is tempting but wrong when src may be nil; see #71844.
// Replace loop and make(...) as maps.Clone(src) returns map[int]string
// which is assignable to M.
var dst M
dst = make(M, len(src))
for key, value := range src {
dst[key] = value // want "Replace m\\[k\\]=v loop with maps.Copy"
}
println(dst)
}
func useClone_typesDiffer4(src map[int]string) {
// Clone is tempting but wrong when src may be nil; see #71844.
// Replace loop and make(...) as maps.Clone(src) returns map[int]string
// which is assignable to M.
var dst M
dst = make(M, len(src))
for key, value := range src {
dst[key] = value // want "Replace m\\[k\\]=v loop with maps.Copy"
}
println(dst)
}
func useClone_generic[Map ~map[K]V, K comparable, V any](src Map) {
// Clone is tempting but wrong when src may be nil; see #71844.
// Replace loop and make(...) by maps.Clone
dst := make(Map, len(src))
for key, value := range src {
dst[key] = value // want "Replace m\\[k\\]=v loop with maps.Copy"
}
println(dst)
}
// -- src is iter.Seq2 --
func useInsert_assignableToSeq2(dst map[int]string, src func(yield func(int, string) bool)) {
// Replace loop by maps.Insert because src is assignable to iter.Seq2.
for k, v := range src {
dst[k] = v // want "Replace m\\[k\\]=v loop with maps.Insert"
}
}
func useCollect(src iter.Seq2[int, string]) {
// Replace loop and make(...) by maps.Collect.
var dst map[int]string
dst = make(map[int]string) // A
// B
for key, value := range src {
// C
dst[key] = value // want "Replace m\\[k\\]=v loop with maps.Collect"
}
}
func useInsert_typesDifferAssign(src iter.Seq2[int, string]) {
// Replace loop and make(...): maps.Collect returns an unnamed map type
// that is assignable to M.
var dst M
dst = make(M)
// A
for key, value := range src {
// B
dst[key] = value // want "Replace m\\[k\\]=v loop with maps.Collect"
}
}
func useInsert_typesDifferDeclare(src iter.Seq2[int, string]) {
// Replace loop but not make(...) as maps.Collect would return an
// unnamed map type that would change the type of dst.
dst := make(M)
for key, value := range src {
dst[key] = value // want "Replace m\\[k\\]=v loop with maps.Insert"
}
}
// -- non-matches --
type isomerOfSeq2 func(yield func(int, string) bool)
func nopeInsertRequiresAssignableToSeq2(dst map[int]string, src isomerOfSeq2) {
for k, v := range src { // nope: src is not assignable to maps.Insert's iter.Seq2 parameter
dst[k] = v
}
}
func nopeSingleVarRange(dst map[int]bool, src map[int]string) {
for key := range src { // nope: must be "for k, v"
dst[key] = true
}
}
func nopeBodyNotASingleton(src map[int]string) {
var dst map[int]string
for key, value := range src {
dst[key] = value
println() // nope: other things in the loop body
}
}
// Regression test for https://github.com/golang/go/issues/70815#issuecomment-2581999787.
func nopeAssignmentHasIncrementOperator(src map[int]int) {
dst := make(map[int]int)
for k, v := range src {
dst[k] += v
}
}
func nopeNotAMap(src map[int]string) {
var dst []string
for k, v := range src {
dst[k] = v
}
}
func nopeNotAMapGeneric[E any, M ~map[int]E, S ~[]E](src M) {
var dst S
for k, v := range src {
dst[k] = v
}
}
func nopeHasImplicitValueWidening(src map[string]int) {
dst := make(map[string]any)
for k, v := range src {
dst[k] = v
}
}
func nopeHasImplicitKeyWidening(src map[string]string) {
dst := make(map[any]string)
for k, v := range src {
dst[k] = v
}
}
// The expression for the map (y[v]) must not itself refer to loop variables.
// See https://go.dev/issue/77008.
func nopeMapExprUsesLoopVars(x map[int]int, y []map[int]int) {
for i, v := range x {
y[v][i] = v
}
}
func nopeExtraKeyValueUsage(x map[int]int, y []map[int]int) {
for i, v := range x {
y[i][i] = v
}
getMap := func(int) map[int]int { return nil }
for i, v := range x {
getMap(i)[i] = v
}
for i, v := range x {
getMap(v)[i] = v
}
}
func nope2LhsAssigment(x map[int]int, y map[int]int) {
for i, v := range x {
y[i], _ = v, 0
}
}
================================================
FILE: go/analysis/passes/modernize/testdata/src/mapsloop/mapsloop.go.golden
================================================
//go:build go1.23
package mapsloop
import (
"iter"
"maps"
)
var _ = maps.Clone[M] // force "maps" import so that each diagnostic doesn't add one
type M map[int]string
// -- src is map --
func useCopy(dst, src map[int]string) {
// Replace loop by maps.Copy.
// A
// want "Replace m\\[k\\]=v loop with maps.Copy"
maps.Copy(dst, src)
}
func useCopyRetrieveMap(x map[int]int) {
getMap := func(int) map[int]int { return nil }
// TODO(yuchen): don't assume that getMap returns the same map each time and has no effects.
//
// So, to avoid changing the cardinality of side effects,
// the limit expression must not involve function calls (e.g. seq.Len()) or channel receives.
// want "Replace m\\[k\\]=v loop with maps.Copy"
maps.Copy(getMap(0), x)
}
func useCopyGeneric[K comparable, V any, M ~map[K]V](dst, src M) {
// Replace loop by maps.Copy.
// A
// want "Replace m\\[k\\]=v loop with maps.Copy"
maps.Copy(dst, src)
}
func useCopyNotClone(src map[int]string) {
// Clone is tempting but wrong when src may be nil; see #71844.
// Replace make(...) by maps.Copy.
dst := make(map[int]string, len(src))
// A
// B
// want "Replace m\\[k\\]=v loop with maps.Copy"
// C
maps.Copy(dst, src)
// A
dst = map[int]string{}
// B
// C
// want "Replace m\\[k\\]=v loop with maps.Copy"
maps.Copy(dst, src)
println(dst)
}
func useCopyParen(src map[int]string) {
// Clone is tempting but wrong when src may be nil; see #71844.
// Replace (make)(...) by maps.Clone.
dst := (make)(map[int]string, len(src))
// want "Replace m\\[k\\]=v loop with maps.Copy"
maps.Copy(dst, src)
dst = (map[int]string{})
// want "Replace m\\[k\\]=v loop with maps.Copy"
maps.Copy(dst, src)
println(dst)
}
func useCopy_typesDiffer(src M) {
// Replace loop but not make(...) as maps.Copy(src) would return wrong type M.
dst := make(map[int]string, len(src))
// want "Replace m\\[k\\]=v loop with maps.Copy"
maps.Copy(dst, src)
println(dst)
}
func useCopy_typesDiffer2(src map[int]string) {
// Replace loop but not make(...) as maps.Copy(src) would return wrong type map[int]string.
dst := make(M, len(src))
// want "Replace m\\[k\\]=v loop with maps.Copy"
maps.Copy(dst, src)
println(dst)
}
func useClone_typesDiffer3(src map[int]string) {
// Clone is tempting but wrong when src may be nil; see #71844.
// Replace loop and make(...) as maps.Clone(src) returns map[int]string
// which is assignable to M.
var dst M
dst = make(M, len(src))
// want "Replace m\\[k\\]=v loop with maps.Copy"
maps.Copy(dst, src)
println(dst)
}
func useClone_typesDiffer4(src map[int]string) {
// Clone is tempting but wrong when src may be nil; see #71844.
// Replace loop and make(...) as maps.Clone(src) returns map[int]string
// which is assignable to M.
var dst M
dst = make(M, len(src))
// want "Replace m\\[k\\]=v loop with maps.Copy"
maps.Copy(dst, src)
println(dst)
}
func useClone_generic[Map ~map[K]V, K comparable, V any](src Map) {
// Clone is tempting but wrong when src may be nil; see #71844.
// Replace loop and make(...) by maps.Clone
dst := make(Map, len(src))
// want "Replace m\\[k\\]=v loop with maps.Copy"
maps.Copy(dst, src)
println(dst)
}
// -- src is iter.Seq2 --
func useInsert_assignableToSeq2(dst map[int]string, src func(yield func(int, string) bool)) {
// Replace loop by maps.Insert because src is assignable to iter.Seq2.
// want "Replace m\\[k\\]=v loop with maps.Insert"
maps.Insert(dst, src)
}
func useCollect(src iter.Seq2[int, string]) {
// Replace loop and make(...) by maps.Collect.
var dst map[int]string
// A
// B
// C
// want "Replace m\\[k\\]=v loop with maps.Collect"
dst = maps.Collect(src)
}
func useInsert_typesDifferAssign(src iter.Seq2[int, string]) {
// Replace loop and make(...): maps.Collect returns an unnamed map type
// that is assignable to M.
var dst M
// A
// B
// want "Replace m\\[k\\]=v loop with maps.Collect"
dst = maps.Collect(src)
}
func useInsert_typesDifferDeclare(src iter.Seq2[int, string]) {
// Replace loop but not make(...) as maps.Collect would return an
// unnamed map type that would change the type of dst.
dst := make(M)
// want "Replace m\\[k\\]=v loop with maps.Insert"
maps.Insert(dst, src)
}
// -- non-matches --
type isomerOfSeq2 func(yield func(int, string) bool)
func nopeInsertRequiresAssignableToSeq2(dst map[int]string, src isomerOfSeq2) {
for k, v := range src { // nope: src is not assignable to maps.Insert's iter.Seq2 parameter
dst[k] = v
}
}
func nopeSingleVarRange(dst map[int]bool, src map[int]string) {
for key := range src { // nope: must be "for k, v"
dst[key] = true
}
}
func nopeBodyNotASingleton(src map[int]string) {
var dst map[int]string
for key, value := range src {
dst[key] = value
println() // nope: other things in the loop body
}
}
// Regression test for https://github.com/golang/go/issues/70815#issuecomment-2581999787.
func nopeAssignmentHasIncrementOperator(src map[int]int) {
dst := make(map[int]int)
for k, v := range src {
dst[k] += v
}
}
func nopeNotAMap(src map[int]string) {
var dst []string
for k, v := range src {
dst[k] = v
}
}
func nopeNotAMapGeneric[E any, M ~map[int]E, S ~[]E](src M) {
var dst S
for k, v := range src {
dst[k] = v
}
}
func nopeHasImplicitValueWidening(src map[string]int) {
dst := make(map[string]any)
for k, v := range src {
dst[k] = v
}
}
func nopeHasImplicitKeyWidening(src map[string]string) {
dst := make(map[any]string)
for k, v := range src {
dst[k] = v
}
}
// The expression for the map (y[v]) must not itself refer to loop variables.
// See https://go.dev/issue/77008.
func nopeMapExprUsesLoopVars(x map[int]int, y []map[int]int) {
for i, v := range x {
y[v][i] = v
}
}
func nopeExtraKeyValueUsage(x map[int]int, y []map[int]int) {
for i, v := range x {
y[i][i] = v
}
getMap := func(int) map[int]int { return nil }
for i, v := range x {
getMap(i)[i] = v
}
for i, v := range x {
getMap(v)[i] = v
}
}
func nope2LhsAssigment(x map[int]int, y map[int]int) {
for i, v := range x {
y[i], _ = v, 0
}
}
================================================
FILE: go/analysis/passes/modernize/testdata/src/mapsloop/mapsloop_dot.go
================================================
//go:build go1.23
package mapsloop
import . "maps"
var _ = Clone[M] // force "maps" import so that each diagnostic doesn't add one
func useCopyDot(dst, src map[int]string) {
// Replace loop by maps.Copy.
for key, value := range src {
dst[key] = value // want "Replace m\\[k\\]=v loop with maps.Copy"
}
}
func useCloneDot(src map[int]string) {
// Clone is tempting but wrong when src may be nil; see #71844.
// Replace make(...) by maps.Copy.
dst := make(map[int]string, len(src))
for key, value := range src {
dst[key] = value // want "Replace m\\[k\\]=v loop with maps.Copy"
}
println(dst)
}
================================================
FILE: go/analysis/passes/modernize/testdata/src/mapsloop/mapsloop_dot.go.golden
================================================
//go:build go1.23
package mapsloop
import . "maps"
var _ = Clone[M] // force "maps" import so that each diagnostic doesn't add one
func useCopyDot(dst, src map[int]string) {
// Replace loop by maps.Copy.
// want "Replace m\\[k\\]=v loop with maps.Copy"
Copy(dst, src)
}
func useCloneDot(src map[int]string) {
// Clone is tempting but wrong when src may be nil; see #71844.
// Replace make(...) by maps.Copy.
dst := make(map[int]string, len(src))
// want "Replace m\\[k\\]=v loop with maps.Copy"
Copy(dst, src)
println(dst)
}
================================================
FILE: go/analysis/passes/modernize/testdata/src/minmax/go120/minmax.go
================================================
package go120
================================================
FILE: go/analysis/passes/modernize/testdata/src/minmax/go120/minmax_go120.go
================================================
//go:build !go1.21
package go120
func min(a, b int) int { // can't be removed because we don't have at least go1.21
if a <= b {
return a
} else {
return b
}
}
================================================
FILE: go/analysis/passes/modernize/testdata/src/minmax/go120/minmax_go120.go.golden
================================================
//go:build !go1.21
package go120
func min(a, b int) int { // can't be removed because we don't have at least go1.21
if a <= b {
return a
} else {
return b
}
}
================================================
FILE: go/analysis/passes/modernize/testdata/src/minmax/minmax.go
================================================
package minmax
func ifmin(a, b int) {
x := a // A
// B
if a < b { // want "if statement can be modernized using max"
// C
x = b // D
// E
}
print(x)
}
func ifmax(a, b int) {
x := a
if a > b { // want "if statement can be modernized using min"
x = b
}
print(x)
}
func ifminvariant(a, b int) {
x := a
if x > b { // want "if statement can be modernized using min"
x = b
}
print(x)
}
func ifmaxvariant(a, b int) {
x := b
if a < x { // want "if statement can be modernized using min"
x = a
}
print(x)
}
func ifelsemin(a, b int) {
var x int // A
// B
if a <= b { // want "if/else statement can be modernized using min"
// C
x = a // D
// E
} else {
// F
x = b // G
// H
}
print(x)
}
func ifelsemax(a, b int) {
// A
var x int // B
// C
if a >= b { // want "if/else statement can be modernized using max"
// D
x = a // E
// F
} else {
// G
x = b
}
print(x)
}
func shadowed() int {
hour, min := 3600, 60
var time int
if hour < min { // silent: the built-in min function is shadowed here
time = hour
} else {
time = min
}
return time
}
func nopeIfStmtHasInitStmt() {
x := 1
if y := 2; y < x { // silent: IfStmt has an Init stmt
x = y
}
print(x)
}
// Regression test for a bug: fix was "y := max(x, y)".
func oops() {
x := 1
y := 2
if x > y { // want "if statement can be modernized using max"
y = x
}
print(y)
}
// Regression test for a bug: += is not a simple assignment.
func nopeAssignHasIncrementOperator() {
x := 1
y := 0
y += 2
if x > y {
y = x
}
print(y)
}
// Regression test for https://github.com/golang/go/issues/71721.
func nopeNotAMinimum(x, y int) int {
// A value of -1 or 0 will use a default value (30).
if x <= 0 {
y = 30
} else {
y = x
}
return y
}
// Regression test for https://github.com/golang/go/issues/71847#issuecomment-2673491596
func nopeHasElseBlock(x int) int {
y := x
// Before, this was erroneously reduced to y = max(x, 0)
if y < 0 {
y = 0
} else {
y += 2
}
return y
}
func fix72727(a, b int) {
o := a - 42
// some important comment. DO NOT REMOVE.
if o < b { // want "if statement can be modernized using max"
o = b
}
}
type myfloat float64
// The built-in min/max differ in their treatment of NaN,
// so reject floating-point numbers (#72829).
func nopeFloat(a, b myfloat) (res myfloat) {
if a < b {
res = a
} else {
res = b
}
return
}
// Regression test for golang/go#72928.
func underscoreAssign(a, b int) {
if a > b {
_ = a
}
}
// Regression test for https://github.com/golang/go/issues/73576.
func nopeIfElseIf(a int) int {
x := 0
if a < 0 {
x = 0
} else if a > 100 {
x = 100
} else {
x = a
}
return x
}
// Regression test for https://go.dev/issue/77671
func selectComm(ch chan int) int {
select {
case n := <-ch:
if n > 100 {
n = 100
}
return n
}
}
================================================
FILE: go/analysis/passes/modernize/testdata/src/minmax/minmax.go.golden
================================================
package minmax
func ifmin(a, b int) {
x := max(
// A
// B
a,
// want "if statement can be modernized using max"
// C
// D
// E
b)
print(x)
}
func ifmax(a, b int) {
x := min(a,
// want "if statement can be modernized using min"
b)
print(x)
}
func ifminvariant(a, b int) {
x := min(a,
// want "if statement can be modernized using min"
b)
print(x)
}
func ifmaxvariant(a, b int) {
x := min(a,
// want "if statement can be modernized using min"
b)
print(x)
}
func ifelsemin(a, b int) {
var x int // A
// B
x = min(
// want "if/else statement can be modernized using min"
// C
// D
// E
a,
// F
// G
// H
b)
print(x)
}
func ifelsemax(a, b int) {
// A
var x int // B
// C
x = max(
// want "if/else statement can be modernized using max"
// D
// E
// F
a,
// G
b)
print(x)
}
func shadowed() int {
hour, min := 3600, 60
var time int
if hour < min { // silent: the built-in min function is shadowed here
time = hour
} else {
time = min
}
return time
}
func nopeIfStmtHasInitStmt() {
x := 1
if y := 2; y < x { // silent: IfStmt has an Init stmt
x = y
}
print(x)
}
// Regression test for a bug: fix was "y := max(x, y)".
func oops() {
x := 1
y := max(x,
// want "if statement can be modernized using max"
2)
print(y)
}
// Regression test for a bug: += is not a simple assignment.
func nopeAssignHasIncrementOperator() {
x := 1
y := 0
y += 2
if x > y {
y = x
}
print(y)
}
// Regression test for https://github.com/golang/go/issues/71721.
func nopeNotAMinimum(x, y int) int {
// A value of -1 or 0 will use a default value (30).
if x <= 0 {
y = 30
} else {
y = x
}
return y
}
// Regression test for https://github.com/golang/go/issues/71847#issuecomment-2673491596
func nopeHasElseBlock(x int) int {
y := x
// Before, this was erroneously reduced to y = max(x, 0)
if y < 0 {
y = 0
} else {
y += 2
}
return y
}
func fix72727(a, b int) {
o := max(
// some important comment. DO NOT REMOVE.
a-42,
// want "if statement can be modernized using max"
b)
}
type myfloat float64
// The built-in min/max differ in their treatment of NaN,
// so reject floating-point numbers (#72829).
func nopeFloat(a, b myfloat) (res myfloat) {
if a < b {
res = a
} else {
res = b
}
return
}
// Regression test for golang/go#72928.
func underscoreAssign(a, b int) {
if a > b {
_ = a
}
}
// Regression test for https://github.com/golang/go/issues/73576.
func nopeIfElseIf(a int) int {
x := 0
if a < 0 {
x = 0
} else if a > 100 {
x = 100
} else {
x = a
}
return x
}
// Regression test for https://go.dev/issue/77671
func selectComm(ch chan int) int {
select {
case n := <-ch:
if n > 100 {
n = 100
}
return n
}
}
================================================
FILE: go/analysis/passes/modernize/testdata/src/minmax/nonstrict/nonstrict.go
================================================
package nonstrict
// min with <= operator - should be detected and removed
func min(a, b int) int { // want "user-defined min function is equivalent to built-in min and can be removed"
if a <= b {
return a
} else {
return b
}
}
// max with >= operator - should be detected and removed
func max(a, b int) int { // want "user-defined max function is equivalent to built-in max and can be removed"
if a >= b {
return a
}
return b
}
================================================
FILE: go/analysis/passes/modernize/testdata/src/minmax/nonstrict/nonstrict.go.golden
================================================
package nonstrict
================================================
FILE: go/analysis/passes/modernize/testdata/src/minmax/userdefined/userdefined.go
================================================
package userdefined
// User-defined min with float parameters - should NOT be removed due to NaN handling
func minFloat(a, b float64) float64 {
if a < b {
return a
} else {
return b
}
}
// User-defined max with float parameters - should NOT be removed due to NaN handling
func maxFloat(a, b float64) float64 {
if a > b {
return a
} else {
return b
}
}
// User-defined function with different name - should NOT be removed
func minimum(a, b int) int {
if a < b {
return a
} else {
return b
}
}
// User-defined min with different logic - should NOT be removed
func minDifferent(a, b int) int {
return a + b // Completely different logic
}
// Method on a type - should NOT be removed
type MyType struct{}
func (m MyType) min(a, b int) int {
if a < b {
return a
} else {
return b
}
}
// Function with wrong signature - should NOT be removed
func minWrongSig(a int) int {
return a
}
// Function with complex body that doesn't match pattern - should NOT be removed
func minComplex(a, b int) int {
println("choosing min")
if a < b {
return a
} else {
return b
}
}
// min returns the smaller of two values.
func min(a, b int) int { // want "user-defined min function is equivalent to built-in min and can be removed"
if a < b {
return a
} else {
return b
}
}
// max returns the larger of two values.
func max(a, b int) int { // want "user-defined max function is equivalent to built-in max and can be removed"
if a > b {
return a
}
return b
}
================================================
FILE: go/analysis/passes/modernize/testdata/src/minmax/userdefined/userdefined.go.golden
================================================
package userdefined
// User-defined min with float parameters - should NOT be removed due to NaN handling
func minFloat(a, b float64) float64 {
if a < b {
return a
} else {
return b
}
}
// User-defined max with float parameters - should NOT be removed due to NaN handling
func maxFloat(a, b float64) float64 {
if a > b {
return a
} else {
return b
}
}
// User-defined function with different name - should NOT be removed
func minimum(a, b int) int {
if a < b {
return a
} else {
return b
}
}
// User-defined min with different logic - should NOT be removed
func minDifferent(a, b int) int {
return a + b // Completely different logic
}
// Method on a type - should NOT be removed
type MyType struct{}
func (m MyType) min(a, b int) int {
if a < b {
return a
} else {
return b
}
}
// Function with wrong signature - should NOT be removed
func minWrongSig(a int) int {
return a
}
// Function with complex body that doesn't match pattern - should NOT be removed
func minComplex(a, b int) int {
println("choosing min")
if a < b {
return a
} else {
return b
}
}
================================================
FILE: go/analysis/passes/modernize/testdata/src/minmax/wrongoperators/wrongoperators.go
================================================
package wrongoperators
// min function with max logic - should NOT be detected (wrong logic)
func min(a, b int) int {
if a >= b { // This is max logic, not min logic
return a
} else {
return b
}
}
// max function with min logic - should NOT be detected (wrong logic)
func max(a, b int) int {
if a <= b { // This is min logic, not max logic
return a
} else {
return b
}
}
================================================
FILE: go/analysis/passes/modernize/testdata/src/minmax/wrongoperators/wrongoperators.go.golden
================================================
package operators
// min function with max logic - should NOT be detected (wrong logic)
func min(a, b int) int {
if a >= b { // This is max logic, not min logic
return a
} else {
return b
}
}
// max function with min logic - should NOT be detected (wrong logic)
func max(a, b int) int {
if a <= b { // This is min logic, not max logic
return a
} else {
return b
}
}
================================================
FILE: go/analysis/passes/modernize/testdata/src/minmax/wrongreturn/wrongreturn.go
================================================
package wrongreturn
// This should NOT be detected as min - it returns the wrong values
func min(x, y int) int {
if x <= y {
return y
} else {
return x
}
}
// This should NOT be detected as max - it returns the wrong values
func max(x, y int) int {
if x > y {
return y
} else {
return x
}
}
================================================
FILE: go/analysis/passes/modernize/testdata/src/minmax/wrongreturn/wrongreturn.go.golden
================================================
package wrongreturn
// This should NOT be detected as min - it returns the wrong values
func min(x, y int) int {
if x <= y {
return y
} else {
return x
}
}
// This should NOT be detected as max - it returns the wrong values
func max(x, y int) int {
if x > y {
return y
} else {
return x
}
}
================================================
FILE: go/analysis/passes/modernize/testdata/src/newexpr/newexpr.go
================================================
//go:build go1.26
package newexpr
// intVar returns a new var whose value is i.
func intVar(i int) *int { return &i } // want `intVar can be an inlinable wrapper around new\(expr\)` intVar:"newlike"
func int64Var(i int64) *int64 { return &i } // want `int64Var can be an inlinable wrapper around new\(expr\)` int64Var:"newlike"
func stringVar(s string) *string { return &s } // want `stringVar can be an inlinable wrapper around new\(expr\)` stringVar:"newlike"
func varOf[T any](x T) *T { return &x } // want `varOf can be an inlinable wrapper around new\(expr\)` varOf:"newlike"
//go:fix inline
func alreadyAnnotated[T any](x T) *T { return &x } // want `alreadyAnnotated can be an inlinable wrapper around new\(expr\)` alreadyAnnotated:"newlike"
var (
s struct {
int
string
}
_ = intVar(123) // want `call of intVar\(x\) can be simplified to new\(x\)`
_ = int64Var(123) // nope: implicit conversion from untyped int to int64
_ = stringVar("abc") // want `call of stringVar\(x\) can be simplified to new\(x\)`
_ = varOf(s) // want `call of varOf\(x\) can be simplified to new\(x\)`
_ = varOf(123) // want `call of varOf\(x\) can be simplified to new\(x\)`
_ = varOf(int64(123)) // want `call of varOf\(x\) can be simplified to new\(x\)`
_ = varOf[int](123) // want `call of varOf\(x\) can be simplified to new\(x\)`
_ = varOf[int64](123) // nope: implicit conversion from untyped int to int64
_ = varOf( // want `call of varOf\(x\) can be simplified to new\(x\)`
varOf(123)) // want `call of varOf\(x\) can be simplified to new\(x\)`
_ = alreadyAnnotated[int](123) // want `call of alreadyAnnotated\(x\) can be simplified to new\(x\)`
)
================================================
FILE: go/analysis/passes/modernize/testdata/src/newexpr/newexpr.go.golden
================================================
//go:build go1.26
package newexpr
// intVar returns a new var whose value is i.
//
//go:fix inline
func intVar(i int) *int { return new(i) } // want `intVar can be an inlinable wrapper around new\(expr\)` intVar:"newlike"
//go:fix inline
func int64Var(i int64) *int64 { return new(i) } // want `int64Var can be an inlinable wrapper around new\(expr\)` int64Var:"newlike"
//go:fix inline
func stringVar(s string) *string { return new(s) } // want `stringVar can be an inlinable wrapper around new\(expr\)` stringVar:"newlike"
//go:fix inline
func varOf[T any](x T) *T { return new(x) } // want `varOf can be an inlinable wrapper around new\(expr\)` varOf:"newlike"
//go:fix inline
func alreadyAnnotated[T any](x T) *T { return new(x) } // want `alreadyAnnotated can be an inlinable wrapper around new\(expr\)` alreadyAnnotated:"newlike"
var (
s struct {
int
string
}
_ = new(123) // want `call of intVar\(x\) can be simplified to new\(x\)`
_ = int64Var(123) // nope: implicit conversion from untyped int to int64
_ = new("abc") // want `call of stringVar\(x\) can be simplified to new\(x\)`
_ = new(s) // want `call of varOf\(x\) can be simplified to new\(x\)`
_ = new(123) // want `call of varOf\(x\) can be simplified to new\(x\)`
_ = new(int64(123)) // want `call of varOf\(x\) can be simplified to new\(x\)`
_ = new(123) // want `call of varOf\(x\) can be simplified to new\(x\)`
_ = varOf[int64](123) // nope: implicit conversion from untyped int to int64
_ = new( // want `call of varOf\(x\) can be simplified to new\(x\)`
new(123)) // want `call of varOf\(x\) can be simplified to new\(x\)`
_ = new(123) // want `call of alreadyAnnotated\(x\) can be simplified to new\(x\)`
)
================================================
FILE: go/analysis/passes/modernize/testdata/src/newexpr/newexpr_go125.go
================================================
package newexpr
================================================
FILE: go/analysis/passes/modernize/testdata/src/omitzero/kube/kube.go
================================================
package kube
type Foo struct {
EmptyStruct struct{} `json:",omitempty"` // nope: the comment below mentions the k-word
}
// +kubebuilder:validation:Optional
type Other struct {
}
================================================
FILE: go/analysis/passes/modernize/testdata/src/omitzero/omitzero.go
================================================
package omitzero
type Foo struct {
EmptyStruct struct{} `json:",omitempty"` // want "Omitempty has no effect on nested struct fields"
}
type Bar struct {
NonEmptyStruct struct{ a int } `json:",omitempty"` // want "Omitempty has no effect on nested struct fields"
}
type C struct {
D string `json:",omitempty"`
}
type R struct {
M string `json:",omitempty"`
}
type A struct {
C C `json:"test,omitempty"` // want "Omitempty has no effect on nested struct fields"
R R `json:"test"`
}
type X struct {
NonEmptyStruct struct{ a int } `json:",omitempty" yaml:",omitempty"` // want "Omitempty has no effect on nested struct fields"
}
type Y struct {
NonEmptyStruct struct{ a int } `yaml:",omitempty" json:",omitempty"` // want "Omitempty has no effect on nested struct fields"
}
================================================
FILE: go/analysis/passes/modernize/testdata/src/omitzero/omitzero.go.golden
================================================
-- Replace omitempty with omitzero (behavior change) --
package omitzero
type Foo struct {
EmptyStruct struct{} `json:",omitzero"` // want "Omitempty has no effect on nested struct fields"
}
type Bar struct {
NonEmptyStruct struct{ a int } `json:",omitzero"` // want "Omitempty has no effect on nested struct fields"
}
type C struct {
D string `json:",omitempty"`
}
type R struct {
M string `json:",omitempty"`
}
type A struct {
C C `json:"test,omitzero"` // want "Omitempty has no effect on nested struct fields"
R R `json:"test"`
}
type X struct {
NonEmptyStruct struct{ a int } `json:",omitzero" yaml:",omitempty"` // want "Omitempty has no effect on nested struct fields"
}
type Y struct {
NonEmptyStruct struct{ a int } `yaml:",omitempty" json:",omitzero"` // want "Omitempty has no effect on nested struct fields"
}
-- Remove redundant omitempty tag --
package omitzero
type Foo struct {
EmptyStruct struct{} // want "Omitempty has no effect on nested struct fields"
}
type Bar struct {
NonEmptyStruct struct{ a int } // want "Omitempty has no effect on nested struct fields"
}
type C struct {
D string `json:",omitempty"`
}
type R struct {
M string `json:",omitempty"`
}
type A struct {
C C `json:"test"` // want "Omitempty has no effect on nested struct fields"
R R `json:"test"`
}
type X struct {
NonEmptyStruct struct{ a int } `yaml:",omitempty"` // want "Omitempty has no effect on nested struct fields"
}
type Y struct {
NonEmptyStruct struct{ a int } `yaml:",omitempty"` // want "Omitempty has no effect on nested struct fields"
}
================================================
FILE: go/analysis/passes/modernize/testdata/src/plusbuild/plusbuild.go
================================================
// want +3 `.build line is no longer needed`
//go:build linux && amd64
// +build linux,amd64
package plusbuild
================================================
FILE: go/analysis/passes/modernize/testdata/src/plusbuild/plusbuild.go.golden
================================================
// want +3 `.build line is no longer needed`
//go:build linux && amd64
package plusbuild
================================================
FILE: go/analysis/passes/modernize/testdata/src/plusbuild/plusbuild2.go
================================================
// This file ensures that the package is non-empty
// in every build configuration.
package plusbuild
================================================
FILE: go/analysis/passes/modernize/testdata/src/rangeint/a/a.go
================================================
package a
type ID int
================================================
FILE: go/analysis/passes/modernize/testdata/src/rangeint/rangeint.go
================================================
package rangeint
import (
"os"
os1 "os"
"rangeint/a"
)
func _(i int, s struct{ i int }, slice []int) {
for i := 0; i < 10; i++ { // want "for loop can be modernized using range over int"
println(i)
}
for j := int(0); j < 10; j++ { // want "for loop can be modernized using range over int"
println(j)
}
for j := int8(0); j < 10; j++ { // want "for loop can be modernized using range over int"
println(j)
}
for j := int16(0); j < 10; j++ { // want "for loop can be modernized using range over int"
println(j)
}
for j := int32(0); j < 10; j++ { // want "for loop can be modernized using range over int"
println(j)
}
for j := int64(0); j < 10; j++ { // want "for loop can be modernized using range over int"
println(j)
}
for j := uint8(0); j < 10; j++ { // want "for loop can be modernized using range over int"
println(j)
}
for j := uint16(0); j < 10; j++ { // want "for loop can be modernized using range over int"
println(j)
}
for j := uint32(0); j < 10; j++ { // want "for loop can be modernized using range over int"
println(j)
}
for j := uint64(0); j < 10; j++ { // want "for loop can be modernized using range over int"
println(j)
}
for j := int8(0.); j < 10; j++ { // want "for loop can be modernized using range over int"
println(j)
}
for j := int8(.0); j < 10; j++ { // want "for loop can be modernized using range over int"
println(j)
}
for j := os.FileMode(0); j < 10; j++ { // want "for loop can be modernized using range over int"
println(j)
}
{
var i int
for i = 0; i < 10; i++ { // want "for loop can be modernized using range over int"
}
// NB: no uses of i after loop.
}
for i := 0; i < 10; i++ { // want "for loop can be modernized using range over int"
// i unused within loop
}
for i := 0; i < len(slice); i++ { // want "for loop can be modernized using range over int"
println(slice[i])
}
for i := 0; i < len(""); i++ { // want "for loop can be modernized using range over int"
// NB: not simplified to range ""
}
// nope
for j := .0; j < 10; j++ { // nope: j is a float type
println(j)
}
for j := float64(0); j < 10; j++ { // nope: j is a float type
println(j)
}
for i := 0; i < 10; { // nope: missing increment
}
for i := 0; i < 10; i-- { // nope: negative increment
}
for i := 0; ; i++ { // nope: missing comparison
}
for i := 0; i <= 10; i++ { // nope: wrong comparison
}
for ; i < 10; i++ { // nope: missing init
}
for s.i = 0; s.i < 10; s.i++ { // nope: not an ident
}
for i := 0; i < 10; i++ { // nope: takes address of i
println(&i)
}
for i := 0; i < 10; i++ { // nope: increments i
i++
}
for i := 0; i < 10; i++ { // nope: assigns i
i = 8
}
for i := 0; i < 10; i += 1 { // want "for loop can be modernized using range over int"
println(i)
}
for i := 0; i < 10; s.i += 1 { // nope: modifies another variable
println(i)
}
for i := 0; i < 10; i -= 1 { // nope: decrements i
println(i)
}
for i := 0; i < 10; i += 2 { // nope: range only increments by 1
println(i)
}
// The limit expression must be loop invariant;
// see https://github.com/golang/go/issues/72917
for i := 0; i < f(); i++ { // nope
}
{
var s struct{ limit int }
for i := 0; i < s.limit; i++ { // nope: limit is not a const or local var
}
}
{
const k = 10
for i := 0; i < k; i++ { // want "for loop can be modernized using range over int"
}
}
{
var limit = 10
for i := 0; i < limit; i++ { // want "for loop can be modernized using range over int"
}
}
{
var limit = 10
for i := 0; i < limit; i++ { // nope: limit is address-taken
}
print(&limit)
}
{
limit := 10
limit++
for i := 0; i < limit; i++ { // nope: limit is assigned other than by its declaration
}
}
for i := 0; i < Global; i++ { // nope: limit is an exported global var; may be updated elsewhere
}
for i := 0; i < len(table); i++ { // want "for loop can be modernized using range over int"
}
{
s := []string{}
for i := 0; i < len(s); i++ { // nope: limit is not loop-invariant
s = s[1:]
}
}
for i := 0; i < len(slice); i++ { // nope: i is incremented within loop
i += 1
}
for Global = 0; Global < 10; Global++ { // nope: loop index is a global variable.
}
}
var Global int
var table = []string{"hello", "world"}
func f() int { return 0 }
// Repro for part of #71847: ("for range n is invalid if the loop body contains i++"):
func _(s string) {
var i int // (this is necessary)
for i = 0; i < len(s); i++ { // nope: loop body increments i
if true {
i++ // nope
}
}
}
// Repro for #71952: for and range loops have different final values
// on i (n and n-1, respectively) so we can't offer the fix if i is
// used after the loop.
func nopePostconditionDiffers() {
i := 0
for i = 0; i < 5; i++ {
println(i)
}
println(i) // must print 5, not 4
}
// Non-integer untyped constants need to be explicitly converted to int.
func issue71847d() {
const limit = 1e3 // float
for i := 0; i < limit; i++ { // want "for loop can be modernized using range over int"
}
for i := int(0); i < limit; i++ { // want "for loop can be modernized using range over int"
}
for i := uint(0); i < limit; i++ { // want "for loop can be modernized using range over int"
}
const limit2 = 1 + 0i // complex
for i := 0; i < limit2; i++ { // want "for loop can be modernized using range over int"
}
}
func issue72726() {
var n, kd int
for i := 0; i < n; i++ { // want "for loop can be modernized using range over int"
// nope: j will be invisible once it's refactored to 'for j := range min(n-j, kd+1)'
for j := 0; j < min(n-j, kd+1); j++ { // nope
_, _ = i, j
}
}
for i := 0; i < i; i++ { // nope
}
var i int
for i = 0; i < i/2; i++ { // nope
}
var arr []int
for i = 0; i < arr[i]; i++ { // nope
}
}
func todo() {
for j := os1.FileMode(0); j < 10; j++ { // want "for loop can be modernized using range over int"
println(j)
}
}
type T uint
type TAlias = uint
func Fn(a int) T {
return T(a)
}
func issue73037() {
var q T
for a := T(0); a < q; a++ { // want "for loop can be modernized using range over int"
println(a)
}
for a := Fn(0); a < q; a++ {
println(a)
}
var qa TAlias
for a := TAlias(0); a < qa; a++ { // want "for loop can be modernized using range over int"
println(a)
}
for a := T(0); a < 10; a++ { // want "for loop can be modernized using range over int"
for b := T(0); b < 10; b++ { // want "for loop can be modernized using range over int"
println(a, b)
}
}
}
func issue75289() {
// A use of i within a defer may be textually before the loop but runs
// after, so it should cause the loop to be rejected as a candidate
// to avoid it observing a different final value of i.
{
var i int
defer func() { println(i) }()
for i = 0; i < 10; i++ { // nope: i is accessed after the loop (via defer)
}
}
// A use of i within a defer within the loop is also a dealbreaker.
{
var i int
for i = 0; i < 10; i++ { // nope: i is accessed after the loop (via defer)
defer func() { println(i) }()
}
}
// This (outer) defer is irrelevant.
defer func() {
var i int
for i = 0; i < 10; i++ { // want "for loop can be modernized using range over int"
}
}()
}
// See go.dev/issue/76880.
func _() (i int) {
for i = 0; i < 3; i++ { // nope: i is implicitly accessed after the loop
}
return
}
func issue74687() {
for i := a.ID(0); i < 10; i++ { // want "for loop can be modernized using range over int"
println(i)
}
for i := a.ID(0); i < a.ID(13); i++ { // want "for loop can be modernized using range over int"
println(i)
}
}
func issue76470() {
var i, j int
UsedAfter:
for i = 0; i < 10; i++ { // nope: i is accessed after the loop
break UsedAfter
}
if i == 9 {
panic("Modernizer changes behavior")
}
NotUsedAfter:
for j = 0; j < 10; j++ { // want "for loop can be modernized using range over int"
break NotUsedAfter
}
}
// Don't allow rewriting of an outer loop when its inner loop modifies the outer
// loop variable.
func issue77034() {
for i := 0; i < 5; i++ {
for i = range 10 {
}
}
}
func issue77034_define_inner() {
for i := 0; i < 5; i++ { // want "for loop can be modernized using range over int"
for i := range 10 { // inner "i" doesn't modify outer "i"
println(i)
}
}
}
type C int32
// Don't add an unneeded type cast if the limit's default type is an integer, and
// the init value is assigned outside the loop, because the compiler can infer the type.
func issue77891() {
const limit_float = 1e6
var i int
for i = 0; i < limit_float; i++ { // want "for loop can be modernized using range over int"
println(i)
}
var j int64
for j = 0; j < 20; j++ { // want "for loop can be modernized using range over int"
println(j)
}
var c C
for c = 0; c < 10; c++ { // want "for loop can be modernized using range over int"
println(c)
}
var ptr uintptr
for ptr = 0; ptr < 100; ptr++ { // want "for loop can be modernized using range over int"
println(ptr)
}
}
================================================
FILE: go/analysis/passes/modernize/testdata/src/rangeint/rangeint.go.golden
================================================
package rangeint
import (
os1 "os"
"rangeint/a"
)
func _(i int, s struct{ i int }, slice []int) {
for i := range 10 { // want "for loop can be modernized using range over int"
println(i)
}
for j := range 10 { // want "for loop can be modernized using range over int"
println(j)
}
for j := range int8(10) { // want "for loop can be modernized using range over int"
println(j)
}
for j := range int16(10) { // want "for loop can be modernized using range over int"
println(j)
}
for j := range int32(10) { // want "for loop can be modernized using range over int"
println(j)
}
for j := range int64(10) { // want "for loop can be modernized using range over int"
println(j)
}
for j := range uint8(10) { // want "for loop can be modernized using range over int"
println(j)
}
for j := range uint16(10) { // want "for loop can be modernized using range over int"
println(j)
}
for j := range uint32(10) { // want "for loop can be modernized using range over int"
println(j)
}
for j := range uint64(10) { // want "for loop can be modernized using range over int"
println(j)
}
for j := range int8(10) { // want "for loop can be modernized using range over int"
println(j)
}
for j := range int8(10) { // want "for loop can be modernized using range over int"
println(j)
}
for j := range os1.FileMode(10) { // want "for loop can be modernized using range over int"
println(j)
}
{
var i int
for i = range 10 { // want "for loop can be modernized using range over int"
}
// NB: no uses of i after loop.
}
for range 10 { // want "for loop can be modernized using range over int"
// i unused within loop
}
for i := range slice { // want "for loop can be modernized using range over int"
println(slice[i])
}
for range len("") { // want "for loop can be modernized using range over int"
// NB: not simplified to range ""
}
// nope
for j := .0; j < 10; j++ { // nope: j is a float type
println(j)
}
for j := float64(0); j < 10; j++ { // nope: j is a float type
println(j)
}
for i := 0; i < 10; { // nope: missing increment
}
for i := 0; i < 10; i-- { // nope: negative increment
}
for i := 0; ; i++ { // nope: missing comparison
}
for i := 0; i <= 10; i++ { // nope: wrong comparison
}
for ; i < 10; i++ { // nope: missing init
}
for s.i = 0; s.i < 10; s.i++ { // nope: not an ident
}
for i := 0; i < 10; i++ { // nope: takes address of i
println(&i)
}
for i := 0; i < 10; i++ { // nope: increments i
i++
}
for i := 0; i < 10; i++ { // nope: assigns i
i = 8
}
for i := range 10 { // want "for loop can be modernized using range over int"
println(i)
}
for i := 0; i < 10; s.i += 1 { // nope: modifies another variable
println(i)
}
for i := 0; i < 10; i -= 1 { // nope: decrements i
println(i)
}
for i := 0; i < 10; i += 2 { // nope: range only increments by 1
println(i)
}
// The limit expression must be loop invariant;
// see https://github.com/golang/go/issues/72917
for i := 0; i < f(); i++ { // nope
}
{
var s struct{ limit int }
for i := 0; i < s.limit; i++ { // nope: limit is not a const or local var
}
}
{
const k = 10
for range k { // want "for loop can be modernized using range over int"
}
}
{
var limit = 10
for range limit { // want "for loop can be modernized using range over int"
}
}
{
var limit = 10
for i := 0; i < limit; i++ { // nope: limit is address-taken
}
print(&limit)
}
{
limit := 10
limit++
for i := 0; i < limit; i++ { // nope: limit is assigned other than by its declaration
}
}
for i := 0; i < Global; i++ { // nope: limit is an exported global var; may be updated elsewhere
}
for range table { // want "for loop can be modernized using range over int"
}
{
s := []string{}
for i := 0; i < len(s); i++ { // nope: limit is not loop-invariant
s = s[1:]
}
}
for i := 0; i < len(slice); i++ { // nope: i is incremented within loop
i += 1
}
for Global = 0; Global < 10; Global++ { // nope: loop index is a global variable.
}
}
var Global int
var table = []string{"hello", "world"}
func f() int { return 0 }
// Repro for part of #71847: ("for range n is invalid if the loop body contains i++"):
func _(s string) {
var i int // (this is necessary)
for i = 0; i < len(s); i++ { // nope: loop body increments i
if true {
i++ // nope
}
}
}
// Repro for #71952: for and range loops have different final values
// on i (n and n-1, respectively) so we can't offer the fix if i is
// used after the loop.
func nopePostconditionDiffers() {
i := 0
for i = 0; i < 5; i++ {
println(i)
}
println(i) // must print 5, not 4
}
// Non-integer untyped constants need to be explicitly converted to int.
func issue71847d() {
const limit = 1e3 // float
for range int(limit) { // want "for loop can be modernized using range over int"
}
for range int(limit) { // want "for loop can be modernized using range over int"
}
for range uint(limit) { // want "for loop can be modernized using range over int"
}
const limit2 = 1 + 0i // complex
for range int(limit2) { // want "for loop can be modernized using range over int"
}
}
func issue72726() {
var n, kd int
for i := range n { // want "for loop can be modernized using range over int"
// nope: j will be invisible once it's refactored to 'for j := range min(n-j, kd+1)'
for j := 0; j < min(n-j, kd+1); j++ { // nope
_, _ = i, j
}
}
for i := 0; i < i; i++ { // nope
}
var i int
for i = 0; i < i/2; i++ { // nope
}
var arr []int
for i = 0; i < arr[i]; i++ { // nope
}
}
func todo() {
for j := range os1.FileMode(10) { // want "for loop can be modernized using range over int"
println(j)
}
}
type T uint
type TAlias = uint
func Fn(a int) T {
return T(a)
}
func issue73037() {
var q T
for a := range q { // want "for loop can be modernized using range over int"
println(a)
}
for a := Fn(0); a < q; a++ {
println(a)
}
var qa TAlias
for a := range qa { // want "for loop can be modernized using range over int"
println(a)
}
for a := range T(10) { // want "for loop can be modernized using range over int"
for b := range T(10) { // want "for loop can be modernized using range over int"
println(a, b)
}
}
}
func issue75289() {
// A use of i within a defer may be textually before the loop but runs
// after, so it should cause the loop to be rejected as a candidate
// to avoid it observing a different final value of i.
{
var i int
defer func() { println(i) }()
for i = 0; i < 10; i++ { // nope: i is accessed after the loop (via defer)
}
}
// A use of i within a defer within the loop is also a dealbreaker.
{
var i int
for i = 0; i < 10; i++ { // nope: i is accessed after the loop (via defer)
defer func() { println(i) }()
}
}
// This (outer) defer is irrelevant.
defer func() {
var i int
for i = range 10 { // want "for loop can be modernized using range over int"
}
}()
}
// See go.dev/issue/76880.
func _() (i int) {
for i = 0; i < 3; i++ { // nope: i is implicitly accessed after the loop
}
return
}
func issue74687() {
for i := range a.ID(10) { // want "for loop can be modernized using range over int"
println(i)
}
for i := range a.ID(13) { // want "for loop can be modernized using range over int"
println(i)
}
}
func issue76470() {
var i, j int
UsedAfter:
for i = 0; i < 10; i++ { // nope: i is accessed after the loop
break UsedAfter
}
if i == 9 {
panic("Modernizer changes behavior")
}
NotUsedAfter:
for j = range(10) { // want "for loop can be modernized using range over int"
break NotUsedAfter
}
}
// Don't allow rewriting of an outer loop when its inner loop modifies the outer
// loop variable.
func issue77034() {
for i := 0; i < 5; i++ {
for i = range 10 {
}
}
}
func issue77034_define_inner() {
for range 5 { // want "for loop can be modernized using range over int"
for i := range 10 { // inner "i" doesn't modify outer "i"
println(i)
}
}
}
type C int32
// Don't add an unneeded type cast if the limit's default type is an integer, and
// the init value is assigned outside the loop, because the compiler can infer the type.
func issue77891() {
const limit_float = 1e6
var i int
for i = range int(limit_float) { // want "for loop can be modernized using range over int"
println(i)
}
var j int64
for j = range 20 { // want "for loop can be modernized using range over int"
println(j)
}
var c C
for c = range 10 { // want "for loop can be modernized using range over int"
println(c)
}
var ptr uintptr
for ptr = range 100 { // want "for loop can be modernized using range over int"
println(ptr)
}
}
================================================
FILE: go/analysis/passes/modernize/testdata/src/reflecttypefor/reflecttypefor.go
================================================
package reflecttypefor
import (
"io"
"reflect"
"time"
)
type A string
type B[T any] int
var (
x any
a A
b B[int]
_ = reflect.TypeOf(x) // nope (dynamic)
_ = reflect.TypeOf(0) // want "reflect.TypeOf call can be simplified using TypeFor"
_ = reflect.TypeOf(nil) // nope (likely a mistake)
_ = reflect.TypeOf(uint(0)) // want "reflect.TypeOf call can be simplified using TypeFor"
_ = reflect.TypeOf(error(nil)) // nope (likely a mistake)
_ = reflect.TypeOf((*error)(nil)) // want "reflect.TypeOf call can be simplified using TypeFor"
_ = reflect.TypeOf(io.Reader(nil)) // nope (likely a mistake)
_ = reflect.TypeOf((io.Reader)(nil)) // nope (likely a mistake)
_ = reflect.TypeOf((*io.Reader)(nil)) // want "reflect.TypeOf call can be simplified using TypeFor"
_ = reflect.TypeOf(*new(time.Time)) // want "reflect.TypeOf call can be simplified using TypeFor"
_ = reflect.TypeOf(time.Time{}) // want "reflect.TypeOf call can be simplified using TypeFor"
_ = reflect.TypeOf(time.Duration(0)) // want "reflect.TypeOf call can be simplified using TypeFor"
_ = reflect.TypeOf(&a) // want "reflect.TypeOf call can be simplified using TypeFor"
_ = reflect.TypeOf(&b) // want "reflect.TypeOf call can be simplified using TypeFor"
_ = reflect.TypeOf([]io.Reader(nil)).Elem() // want "reflect.TypeOf call can be simplified using TypeFor"
_ = reflect.TypeOf([]*io.Reader(nil)).Elem() // want "reflect.TypeOf call can be simplified using TypeFor"
_ = reflect.TypeOf((*io.Reader)(nil)).Elem() // want "reflect.TypeOf call can be simplified using TypeFor"
_ = reflect.TypeOf([0]io.Reader{}).Elem() // want "reflect.TypeOf call can be simplified using TypeFor"
_ = reflect.TypeOf([1]io.Reader{nil}).Elem() // want "reflect.TypeOf call can be simplified using TypeFor"
_ = reflect.TypeOf([...]io.Reader{}).Elem() // want "reflect.TypeOf call can be simplified using TypeFor"
_ = reflect.TypeOf([...]io.Reader{nil}).Elem() // want "reflect.TypeOf call can be simplified using TypeFor"
_ = reflect.TypeOf(chan int(nil)).Elem() // want "reflect.TypeOf call can be simplified using TypeFor"
_ = reflect.TypeOf(map[string]int(nil)).Elem() // want "reflect.TypeOf call can be simplified using TypeFor"
)
// Eliminate local var if we deleted its last use.
func _() {
// Test for shadowed nil
nil := "nil"
_ = reflect.TypeOf(nil) // want "reflect.TypeOf call can be simplified using TypeFor"
_ = nil // shadowed nil has multiple uses
var zero string
_ = reflect.TypeOf(zero) // want "reflect.TypeOf call can be simplified using TypeFor"
var z2 string
_ = reflect.TypeOf(z2) // want "reflect.TypeOf call can be simplified using TypeFor"
_ = z2 // z2 has multiple uses
}
type T struct {
f struct {
A bool
B int
C string
}
}
type S struct {
f [2]struct {
A bool
B int
C string
}
}
type R []struct {
A int
}
type M[T struct{ F int }] int
type P struct {
f interface {
}
g func() // fine length
long func(a int, b int, c int) (bool, string, int) // too long
s func(a struct{})
q func() struct{}
}
func f(t *T, r R, m *M[struct{ F int }], s *S, p *P) {
// No suggested fix for all of the following because the type is complicated -- e.g. has an unnamed struct,
// interface, or signature -- so the fix would be more verbose than the original expression.
// Also because structs and interfaces often acquire new fields and methods, and the type string
// produced by this modernizer won't get updated automatically, potentially causing a bug.
_ = reflect.TypeOf(&t.f)
_ = reflect.TypeOf(r[0])
_ = reflect.TypeOf(m)
_ = reflect.TypeOf(&s.f)
_ = reflect.TypeOf(&p.f)
_ = reflect.TypeOf(&p.g)
_ = reflect.TypeOf(&p.long)
_ = reflect.TypeOf(&p.q)
_ = reflect.TypeOf(&p.s)
}
================================================
FILE: go/analysis/passes/modernize/testdata/src/reflecttypefor/reflecttypefor.go.golden
================================================
package reflecttypefor
import (
"io"
"reflect"
"time"
)
type A string
type B[T any] int
var (
x any
a A
b B[int]
_ = reflect.TypeOf(x) // nope (dynamic)
_ = reflect.TypeFor[int]() // want "reflect.TypeOf call can be simplified using TypeFor"
_ = reflect.TypeOf(nil) // nope (likely a mistake)
_ = reflect.TypeFor[uint]() // want "reflect.TypeOf call can be simplified using TypeFor"
_ = reflect.TypeOf(error(nil)) // nope (likely a mistake)
_ = reflect.TypeFor[*error]() // want "reflect.TypeOf call can be simplified using TypeFor"
_ = reflect.TypeOf(io.Reader(nil)) // nope (likely a mistake)
_ = reflect.TypeOf((io.Reader)(nil)) // nope (likely a mistake)
_ = reflect.TypeFor[*io.Reader]() // want "reflect.TypeOf call can be simplified using TypeFor"
_ = reflect.TypeFor[time.Time]() // want "reflect.TypeOf call can be simplified using TypeFor"
_ = reflect.TypeFor[time.Time]() // want "reflect.TypeOf call can be simplified using TypeFor"
_ = reflect.TypeFor[time.Duration]() // want "reflect.TypeOf call can be simplified using TypeFor"
_ = reflect.TypeFor[*A]() // want "reflect.TypeOf call can be simplified using TypeFor"
_ = reflect.TypeFor[*B[int]]() // want "reflect.TypeOf call can be simplified using TypeFor"
_ = reflect.TypeFor[io.Reader]() // want "reflect.TypeOf call can be simplified using TypeFor"
_ = reflect.TypeFor[*io.Reader]() // want "reflect.TypeOf call can be simplified using TypeFor"
_ = reflect.TypeFor[io.Reader]() // want "reflect.TypeOf call can be simplified using TypeFor"
_ = reflect.TypeFor[io.Reader]() // want "reflect.TypeOf call can be simplified using TypeFor"
_ = reflect.TypeFor[io.Reader]() // want "reflect.TypeOf call can be simplified using TypeFor"
_ = reflect.TypeFor[io.Reader]() // want "reflect.TypeOf call can be simplified using TypeFor"
_ = reflect.TypeFor[io.Reader]() // want "reflect.TypeOf call can be simplified using TypeFor"
_ = reflect.TypeFor[int]() // want "reflect.TypeOf call can be simplified using TypeFor"
_ = reflect.TypeFor[int]() // want "reflect.TypeOf call can be simplified using TypeFor"
)
// Eliminate local var if we deleted its last use.
func _() {
// Test for shadowed nil
nil := "nil"
_ = reflect.TypeFor[string]() // want "reflect.TypeOf call can be simplified using TypeFor"
_ = nil // shadowed nil has multiple uses
_ = reflect.TypeFor[string]() // want "reflect.TypeOf call can be simplified using TypeFor"
var z2 string
_ = reflect.TypeFor[string]() // want "reflect.TypeOf call can be simplified using TypeFor"
_ = z2 // z2 has multiple uses
}
type T struct {
f struct {
A bool
B int
C string
}
}
type S struct {
f [2]struct {
A bool
B int
C string
}
}
type R []struct {
A int
}
type M[T struct{ F int }] int
type P struct {
f interface {
}
g func() // fine length
long func(a int, b int, c int) (bool, string, int) // too long
s func(a struct{})
q func() struct{}
}
func f(t *T, r R, m *M[struct{ F int }], s *S, p *P) {
// No suggested fix for all of the following because the type is complicated -- e.g. has an unnamed struct,
// interface, or signature -- so the fix would be more verbose than the original expression.
// Also because structs and interfaces often acquire new fields and methods, and the type string
// produced by this modernizer won't get updated automatically, potentially causing a bug.
_ = reflect.TypeOf(&t.f)
_ = reflect.TypeOf(r[0])
_ = reflect.TypeOf(m)
_ = reflect.TypeOf(&s.f)
_ = reflect.TypeOf(&p.f)
_ = reflect.TypeOf(&p.g)
_ = reflect.TypeOf(&p.long)
_ = reflect.TypeOf(&p.q)
_ = reflect.TypeOf(&p.s)
}
================================================
FILE: go/analysis/passes/modernize/testdata/src/slicescontains/slicescontains.go
================================================
package slicescontains
import (
"fmt"
"slices"
)
var _ = slices.Contains[[]int] // force import of "slices" to avoid duplicate import edits
func nopeNoBreak(slice []int, needle int) {
for i := range slice {
if slice[i] == needle {
println("found")
}
}
}
func rangeIndex(slice []int, needle int) {
for i := range slice { // want "Loop can be simplified using slices.Contains"
if slice[i] == needle {
println("found")
break
}
}
}
func rangeValue(slice []int, needle int) {
for _, elem := range slice { // want "Loop can be simplified using slices.Contains"
if elem == needle {
println("found")
break
}
}
}
func returns(slice []int, needle int) {
for i := range slice { // want "Loop can be simplified using slices.Contains"
if slice[i] == needle {
println("found")
return
}
}
}
func assignTrueBreak(slice []int, needle int) {
found := false
for _, elem := range slice { // want "Loop can be simplified using slices.Contains"
if elem == needle {
found = true
break
}
}
print(found)
}
func assignFalseBreak(slice []int, needle int) {
found := true
for _, elem := range slice { // want "Loop can be simplified using slices.Contains"
if elem == needle {
found = false
break
}
}
print(found)
}
func assignFalseBreakInSelectSwitch(slice []int, needle int) {
// Exercise RangeStmt in CommClause, CaseClause.
select {
default:
found := false
for _, elem := range slice { // want "Loop can be simplified using slices.Contains"
if elem == needle {
found = true
break
}
}
print(found)
}
switch {
default:
found := false
for _, elem := range slice { // want "Loop can be simplified using slices.Contains"
if elem == needle {
found = true
break
}
}
print(found)
}
}
func returnTrue(slice []int, needle int) bool {
for _, elem := range slice { // want "Loop can be simplified using slices.Contains"
if elem == needle {
return true
}
}
return false
}
func returnFalse(slice []int, needle int) bool {
for _, elem := range slice { // want "Loop can be simplified using slices.Contains"
if elem == needle {
return false
}
}
return true
}
func containsFunc(slice []int, needle int) bool {
for _, elem := range slice { // want "Loop can be simplified using slices.ContainsFunc"
if predicate(elem) {
return true
}
}
return false
}
func nopeLoopBodyHasFreeContinuation(slice []int, needle int) bool {
for _, elem := range slice {
if predicate(elem) {
if needle == 7 {
continue // this statement defeats loop elimination
}
return true
}
}
return false
}
func generic[T any](slice []T, f func(T) bool) bool {
for _, elem := range slice { // want "Loop can be simplified using slices.ContainsFunc"
if f(elem) {
return true
}
}
return false
}
func predicate(int) bool
// Regression tests for bad fixes when needle
// and haystack have different types (#71313):
func nopeNeedleHaystackDifferentTypes(x any, args []error) {
for _, arg := range args {
if arg == x {
return
}
}
}
func nopeNeedleHaystackDifferentTypes2(x error, args []any) {
for _, arg := range args {
if arg == x {
return
}
}
}
func nopeVariadicNamedContainsFunc(slice []int) bool {
for _, elem := range slice {
if variadicPredicate(elem) {
return true
}
}
return false
}
func variadicPredicate(int, ...any) bool
func nopeVariadicContainsFunc(slice []int) bool {
f := func(int, ...any) bool {
return true
}
for _, elem := range slice {
if f(elem) {
return true
}
}
return false
}
// Negative test case for implicit C->I conversion
type I interface{ F() }
type C int
func (C) F() {}
func nopeImplicitConversionContainsFunc(slice []C, f func(I) bool) bool {
for _, elem := range slice {
if f(elem) { // implicit conversion from C to I
return true
}
}
return false
}
func nopeTypeParamWidening[T any](slice []T, f func(any) bool) bool {
for _, elem := range slice {
if f(elem) { // implicit conversion from T to any
return true
}
}
return false
}
func issue76210(haystack []string, needle string) bool {
res := len(haystack) == 0
for _, elem := range haystack { // want "Loop can be simplified using slices.Contains"
if needle == elem {
res = true
break
}
}
return res
}
func issue76210negation(haystack []string, needle string) bool {
res := len(haystack) != 0
for _, elem := range haystack { // want "Loop can be simplified using slices.Contains"
if needle == elem {
res = false
break
}
}
return res
}
func issue77677emptybody(slice []int, needle int) {
for _, elem := range slice { // nope: would produce "if slices.Contains" with an empty body
if elem == needle {
break
}
}
}
type Object struct{}
func (o Object) Do() Object { return Object{} }
func (o Object) Print() { fmt.Println("Hello, World!") }
func issue78149nonboolassign(obj Object, opts ...string) {
o := obj
for _, opt := range opts { // want "Loop can be simplified using slices.Contains"
if opt == "something" {
o = o.Do()
break
}
}
o.Print()
}
================================================
FILE: go/analysis/passes/modernize/testdata/src/slicescontains/slicescontains.go.golden
================================================
package slicescontains
import (
"fmt"
"slices"
)
var _ = slices.Contains[[]int] // force import of "slices" to avoid duplicate import edits
func nopeNoBreak(slice []int, needle int) {
for i := range slice {
if slice[i] == needle {
println("found")
}
}
}
func rangeIndex(slice []int, needle int) {
if slices.Contains(slice, needle) {
println("found")
}
}
func rangeValue(slice []int, needle int) {
if slices.Contains(slice, needle) {
println("found")
}
}
func returns(slice []int, needle int) {
if slices.Contains(slice, needle) {
println("found")
return
}
}
func assignTrueBreak(slice []int, needle int) {
found := slices.Contains(slice, needle)
print(found)
}
func assignFalseBreak(slice []int, needle int) {
found := !slices.Contains(slice, needle)
print(found)
}
func assignFalseBreakInSelectSwitch(slice []int, needle int) {
// Exercise RangeStmt in CommClause, CaseClause.
select {
default:
found := slices.Contains(slice, needle)
print(found)
}
switch {
default:
found := slices.Contains(slice, needle)
print(found)
}
}
func returnTrue(slice []int, needle int) bool {
return slices.Contains(slice, needle)
}
func returnFalse(slice []int, needle int) bool {
return !slices.Contains(slice, needle)
}
func containsFunc(slice []int, needle int) bool {
return slices.ContainsFunc(slice, predicate)
}
func nopeLoopBodyHasFreeContinuation(slice []int, needle int) bool {
for _, elem := range slice {
if predicate(elem) {
if needle == 7 {
continue // this statement defeats loop elimination
}
return true
}
}
return false
}
func generic[T any](slice []T, f func(T) bool) bool {
return slices.ContainsFunc(slice, f)
}
func predicate(int) bool
// Regression tests for bad fixes when needle
// and haystack have different types (#71313):
func nopeNeedleHaystackDifferentTypes(x any, args []error) {
for _, arg := range args {
if arg == x {
return
}
}
}
func nopeNeedleHaystackDifferentTypes2(x error, args []any) {
for _, arg := range args {
if arg == x {
return
}
}
}
func nopeVariadicNamedContainsFunc(slice []int) bool {
for _, elem := range slice {
if variadicPredicate(elem) {
return true
}
}
return false
}
func variadicPredicate(int, ...any) bool
func nopeVariadicContainsFunc(slice []int) bool {
f := func(int, ...any) bool {
return true
}
for _, elem := range slice {
if f(elem) {
return true
}
}
return false
}
// Negative test case for implicit C->I conversion
type I interface{ F() }
type C int
func (C) F() {}
func nopeImplicitConversionContainsFunc(slice []C, f func(I) bool) bool {
for _, elem := range slice {
if f(elem) { // implicit conversion from C to I
return true
}
}
return false
}
func nopeTypeParamWidening[T any](slice []T, f func(any) bool) bool {
for _, elem := range slice {
if f(elem) { // implicit conversion from T to any
return true
}
}
return false
}
func issue76210(haystack []string, needle string) bool {
res := len(haystack) == 0
if slices.Contains(haystack, needle) {
res = true
}
return res
}
func issue76210negation(haystack []string, needle string) bool {
res := len(haystack) != 0
if slices.Contains(haystack, needle) {
res = false
}
return res
}
func issue77677emptybody(slice []int, needle int) {
for _, elem := range slice { // nope: would produce "if slices.Contains" with an empty body
if elem == needle {
break
}
}
}
type Object struct{}
func (o Object) Do() Object { return Object{} }
func (o Object) Print() { fmt.Println("Hello, World!") }
func issue78149nonboolassign(obj Object, opts ...string) {
o := obj
if slices.Contains(opts, "something") {
o = o.Do()
}
o.Print()
}
================================================
FILE: go/analysis/passes/modernize/testdata/src/slicesdelete/slicesdelete.go
================================================
package slicesdelete
var g struct{ f []int }
func h() []int { return []int{} }
var ch chan []int
func slicesdelete(test, other []byte, i int) {
const k = 1
_ = append(test[:i], test[i+1:]...) // want "Replace append with slices.Delete"
_ = append(test[:i+1], test[i+2:]...) // want "Replace append with slices.Delete"
_ = append(test[:i+1], test[i+1:]...) // not deleting any slice elements
_ = append(test[:i], test[i-1:]...) // not deleting any slice elements
_ = append(test[:i-1], test[i:]...) // want "Replace append with slices.Delete"
_ = append(test[:i-2], test[i+1:]...) // want "Replace append with slices.Delete"
_ = append(test[:i-2], other[i+1:]...) // different slices "test" and "other"
_ = append(test[:i-2], other[i+1+k:]...) // cannot verify a < b
_ = append(test[:i-2], test[11:]...) // cannot verify a < b
_ = append(test[:1], test[3:]...) // want "Replace append with slices.Delete"
_ = append(g.f[:i], g.f[i+k:]...) // want "Replace append with slices.Delete"
_ = append(h()[:i], h()[i+1:]...) // potentially has side effects
_ = append((<-ch)[:i], (<-ch)[i+1:]...) // has side effects
_ = append(test[:3], test[i+1:]...) // cannot verify a < b
_ = append(test[:i-4], test[i-1:]...) // want "Replace append with slices.Delete"
_ = append(test[:1+2], test[3+4:]...) // want "Replace append with slices.Delete"
_ = append(test[:1+2], test[i-1:]...) // cannot verify a < b
}
func issue73663(test, other []byte, i int32) {
const k = 1
_ = append(test[:i], test[i+1:]...) // want "Replace append with slices.Delete"
_ = append(test[:i-1], test[i:]...) // want "Replace append with slices.Delete"
_ = append(g.f[:i], g.f[i+k:]...) // want "Replace append with slices.Delete"
type int string // int is shadowed, so no offered fix.
_ = append(test[:i], test[i+1:]...)
}
================================================
FILE: go/analysis/passes/modernize/testdata/src/slicesdelete/slicesdelete.go.golden
================================================
package slicesdelete
import "slices"
var g struct{ f []int }
func h() []int { return []int{} }
var ch chan []int
func slicesdelete(test, other []byte, i int) {
const k = 1
_ = slices.Delete(test, i, i+1) // want "Replace append with slices.Delete"
_ = slices.Delete(test, i+1, i+2) // want "Replace append with slices.Delete"
_ = append(test[:i+1], test[i+1:]...) // not deleting any slice elements
_ = append(test[:i], test[i-1:]...) // not deleting any slice elements
_ = slices.Delete(test, i-1, i) // want "Replace append with slices.Delete"
_ = slices.Delete(test, i-2, i+1) // want "Replace append with slices.Delete"
_ = append(test[:i-2], other[i+1:]...) // different slices "test" and "other"
_ = append(test[:i-2], other[i+1+k:]...) // cannot verify a < b
_ = append(test[:i-2], test[11:]...) // cannot verify a < b
_ = slices.Delete(test, 1, 3) // want "Replace append with slices.Delete"
_ = slices.Delete(g.f, i, i+k) // want "Replace append with slices.Delete"
_ = append(h()[:i], h()[i+1:]...) // potentially has side effects
_ = append((<-ch)[:i], (<-ch)[i+1:]...) // has side effects
_ = append(test[:3], test[i+1:]...) // cannot verify a < b
_ = slices.Delete(test, i-4, i-1) // want "Replace append with slices.Delete"
_ = slices.Delete(test, 1+2, 3+4) // want "Replace append with slices.Delete"
_ = append(test[:1+2], test[i-1:]...) // cannot verify a < b
}
func issue73663(test, other []byte, i int32) {
const k = 1
_ = slices.Delete(test, int(i), int(i+1)) // want "Replace append with slices.Delete"
_ = slices.Delete(test, int(i-1), int(i)) // want "Replace append with slices.Delete"
_ = slices.Delete(g.f, int(i), int(i+k)) // want "Replace append with slices.Delete"
type int string // int is shadowed, so no offered fix.
_ = append(test[:i], test[i+1:]...)
}
================================================
FILE: go/analysis/passes/modernize/testdata/src/slicessort/slicessort.go
================================================
package slicessort
import "sort"
type myint int
func _(s []myint) {
sort.Slice(s, func(i, j int) bool { return s[i] < s[j] }) // want "sort.Slice can be modernized using slices.Sort"
}
func _(x *struct{ s []int }) {
sort.Slice(x.s, func(first, second int) bool { return x.s[first] < x.s[second] }) // want "sort.Slice can be modernized using slices.Sort"
}
func _(s []int) {
sort.Slice(s, func(i, j int) bool { return s[i] > s[j] }) // nope: wrong comparison operator
}
func _(s []int) {
sort.Slice(s, func(i, j int) bool { return s[j] < s[i] }) // nope: wrong index var
}
func _(sense bool, s2 []struct{ x int }) {
sort.Slice(s2, func(i, j int) bool { return s2[i].x < s2[j].x }) // nope: not a simple index operation
// Regression test for a crash: the sole statement of a
// comparison func body is not necessarily a return!
sort.Slice(s2, func(i, j int) bool {
if sense {
return s2[i].x < s2[j].x
} else {
return s2[i].x > s2[j].x
}
})
}
================================================
FILE: go/analysis/passes/modernize/testdata/src/slicessort/slicessort.go.golden
================================================
package slicessort
import "slices"
import "sort"
type myint int
func _(s []myint) {
slices.Sort(s) // want "sort.Slice can be modernized using slices.Sort"
}
func _(x *struct{ s []int }) {
slices.Sort(x.s) // want "sort.Slice can be modernized using slices.Sort"
}
func _(s []int) {
sort.Slice(s, func(i, j int) bool { return s[i] > s[j] }) // nope: wrong comparison operator
}
func _(s []int) {
sort.Slice(s, func(i, j int) bool { return s[j] < s[i] }) // nope: wrong index var
}
func _(sense bool, s2 []struct{ x int }) {
sort.Slice(s2, func(i, j int) bool { return s2[i].x < s2[j].x }) // nope: not a simple index operation
// Regression test for a crash: the sole statement of a
// comparison func body is not necessarily a return!
sort.Slice(s2, func(i, j int) bool {
if sense {
return s2[i].x < s2[j].x
} else {
return s2[i].x > s2[j].x
}
})
}
================================================
FILE: go/analysis/passes/modernize/testdata/src/slicessort/slicessort_dot.go
================================================
package slicessort
import (
. "slices"
"sort"
)
func _(s []myint) {
sort.Slice(s, func(i, j int) bool { return s[i] < s[j] }) // want "sort.Slice can be modernized using slices.Sort"
}
func _(x *struct{ s []int }) {
sort.Slice(x.s, func(first, second int) bool { return x.s[first] < x.s[second] }) // want "sort.Slice can be modernized using slices.Sort"
}
func _(s []int) {
sort.Slice(s, func(i, j int) bool { return s[i] > s[j] }) // nope: wrong comparison operator
}
func _(s []int) {
sort.Slice(s, func(i, j int) bool { return s[j] < s[i] }) // nope: wrong index var
}
func _(s2 []struct{ x int }) {
sort.Slice(s2, func(i, j int) bool { return s2[i].x < s2[j].x }) // nope: not a simple index operation
}
func _() { Clip([]int{}) }
================================================
FILE: go/analysis/passes/modernize/testdata/src/slicessort/slicessort_dot.go.golden
================================================
package slicessort
import (
. "slices"
"sort"
)
func _(s []myint) {
Sort(s) // want "sort.Slice can be modernized using slices.Sort"
}
func _(x *struct{ s []int }) {
Sort(x.s) // want "sort.Slice can be modernized using slices.Sort"
}
func _(s []int) {
sort.Slice(s, func(i, j int) bool { return s[i] > s[j] }) // nope: wrong comparison operator
}
func _(s []int) {
sort.Slice(s, func(i, j int) bool { return s[j] < s[i] }) // nope: wrong index var
}
func _(s2 []struct{ x int }) {
sort.Slice(s2, func(i, j int) bool { return s2[i].x < s2[j].x }) // nope: not a simple index operation
}
func _() { Clip([]int{}) }
================================================
FILE: go/analysis/passes/modernize/testdata/src/splitseq/splitseq.go
================================================
//go:build go1.24
package splitseq
import (
"bytes"
"strings"
)
func _() {
for _, line := range strings.Split("", "") { // want "Ranging over SplitSeq is more efficient"
println(line)
}
for i, line := range strings.Split("", "") { // nope: uses index var
println(i, line)
}
for i, _ := range strings.Split("", "") { // nope: uses index var
println(i)
}
for i := range strings.Split("", "") { // nope: uses index var
println(i)
}
for _ = range strings.Split("", "") { // want "Ranging over SplitSeq is more efficient"
}
for range strings.Split("", "") { // want "Ranging over SplitSeq is more efficient"
}
for range bytes.Split(nil, nil) { // want "Ranging over SplitSeq is more efficient"
}
{
lines := strings.Split("", "") // want "Ranging over SplitSeq is more efficient"
for _, line := range lines {
println(line)
}
}
{
lines := strings.Split("", "") // nope: lines is used not just by range
for _, line := range lines {
println(line)
}
println(lines)
}
}
================================================
FILE: go/analysis/passes/modernize/testdata/src/splitseq/splitseq.go.golden
================================================
//go:build go1.24
package splitseq
import (
"bytes"
"strings"
)
func _() {
for line := range strings.SplitSeq("", "") { // want "Ranging over SplitSeq is more efficient"
println(line)
}
for i, line := range strings.Split("", "") { // nope: uses index var
println(i, line)
}
for i, _ := range strings.Split("", "") { // nope: uses index var
println(i)
}
for i := range strings.Split("", "") { // nope: uses index var
println(i)
}
for range strings.SplitSeq("", "") { // want "Ranging over SplitSeq is more efficient"
}
for range strings.SplitSeq("", "") { // want "Ranging over SplitSeq is more efficient"
}
for range bytes.SplitSeq(nil, nil) { // want "Ranging over SplitSeq is more efficient"
}
{
lines := strings.SplitSeq("", "") // want "Ranging over SplitSeq is more efficient"
for line := range lines {
println(line)
}
}
{
lines := strings.Split("", "") // nope: lines is used not just by range
for _, line := range lines {
println(line)
}
println(lines)
}
}
================================================
FILE: go/analysis/passes/modernize/testdata/src/splitseq/splitseq_go123.go
================================================
package splitseq
================================================
FILE: go/analysis/passes/modernize/testdata/src/stditerators/stditerators.go
================================================
package stditerators
import (
"go/types"
"reflect"
)
func _(tuple *types.Tuple) {
for i := 0; i < tuple.Len(); i++ { // want "Len/At loop can simplified using Tuple.Variables iteration"
print(tuple.At(i))
}
}
func _(scope *types.Scope) {
for i := 0; i < scope.NumChildren(); i++ { // want "NumChildren/Child loop can simplified using Scope.Children iteration"
print(scope.Child(i))
}
{
// tests of shadowing of preferred name at def
const child = 0
for i := 0; i < scope.NumChildren(); i++ { // want "NumChildren/Child loop can simplified using Scope.Children iteration"
print(scope.Child(i))
}
for i := 0; i < scope.NumChildren(); i++ { // want "NumChildren/Child loop can simplified using Scope.Children iteration"
print(scope.Child(i), child)
}
}
{
for i := 0; i < scope.NumChildren(); i++ {
const child = 0 // nope: shadowing of fresh name at use
print(scope.Child(i))
}
}
{
for i := 0; i < scope.NumChildren(); i++ { // want "NumChildren/Child loop can simplified using Scope.Children iteration"
elem := scope.Child(i) // => preferred name = "elem"
print(elem)
}
}
{
for i := 0; i < scope.NumChildren(); i++ { // want "NumChildren/Child loop can simplified using Scope.Children iteration"
first := scope.Child(0) // the name heuristic should not be fooled by this
print(first, scope.Child(i))
}
}
}
func _(union, union2 *types.Union) {
for i := 0; i < union.Len(); i++ { // want "Len/Term loop can simplified using Union.Terms iteration"
print(union.Term(i))
print(union.Term(i))
}
for i := union.Len() - 1; i >= 0; i-- { // nope: wrong loop form
print(union.Term(i))
}
for i := 0; i <= union.Len(); i++ { // nope: wrong loop form
print(union.Term(i))
}
for i := 0; i <= union.Len(); i++ { // nope: use of i not in x.At(i)
print(i, union.Term(i))
}
for i := 0; i <= union.Len(); i++ { // nope: x.At and x.Len have different receivers
print(i, union2.Term(i))
}
}
func _(tuple *types.Tuple) {
for i := 0; i < tuple.Len(); i++ { // want "Len/At loop can simplified using Tuple.Variables iteration"
if foo := tuple.At(i); true { // => preferred name = "foo"
print(foo)
}
bar := tuple.At(i)
print(bar)
}
{
// The name v is already declared, but not
// used in the loop, so we can use it again.
v := 1
print(v)
for i := 0; i < tuple.Len(); i++ { // want "Len/At loop can simplified using Tuple.Variables iteration"
print(tuple.At(i))
}
}
{
// The name v is used from the loop, so
// we must choose a fresh name.
v := 1
for i := 0; i < tuple.Len(); i++ { // want "Len/At loop can simplified using Tuple.Variables iteration"
print(tuple.At(i), v)
}
}
}
func _(t reflect.Type) {
for i := 0; i < t.NumField(); i++ { // want "NumField/Field loop can simplified using Type.Fields iteration"
print(t.Field(i))
}
for i := 0; i < t.NumMethod(); i++ { // want "NumMethod/Method loop can simplified using Type.Methods iteration"
print(t.Method(i))
}
for i := 0; i < t.NumIn(); i++ { // want "NumIn/In loop can simplified using Type.Ins iteration"
print(t.In(i))
}
for i := 0; i < t.NumOut(); i++ { // want "NumOut/Out loop can simplified using Type.Outs iteration"
print(t.Out(i))
}
}
func _(v reflect.Value) {
for i := 0; i < v.NumField(); i++ { // want "NumField/Field loop can simplified using Value.Fields iteration"
print(v.Field(i))
}
// Ideally we would use both parts of Value.Field's iter.Seq2 here.
for i := 0; i < v.NumField(); i++ {
print(v.Field(i), v.Type().Field(i)) // nope
}
for i := 0; i < v.NumMethod(); i++ { // want "NumMethod/Method loop can simplified using Value.Methods iteration"
print(v.Method(i))
}
}
================================================
FILE: go/analysis/passes/modernize/testdata/src/stditerators/stditerators.go.golden
================================================
package stditerators
import (
"go/types"
"reflect"
)
func _(tuple *types.Tuple) {
for v := range tuple.Variables() { // want "Len/At loop can simplified using Tuple.Variables iteration"
print(v)
}
}
func _(scope *types.Scope) {
for child := range scope.Children() { // want "NumChildren/Child loop can simplified using Scope.Children iteration"
print(child)
}
{
// tests of shadowing of preferred name at def
const child = 0
for child := range scope.Children() { // want "NumChildren/Child loop can simplified using Scope.Children iteration"
print(child)
}
for child0 := range scope.Children() { // want "NumChildren/Child loop can simplified using Scope.Children iteration"
print(child0, child)
}
}
{
for i := 0; i < scope.NumChildren(); i++ {
const child = 0 // nope: shadowing of fresh name at use
print(scope.Child(i))
}
}
{
for elem := range scope.Children() { // want "NumChildren/Child loop can simplified using Scope.Children iteration"
elem := elem // => preferred name = "elem"
print(elem)
}
}
{
for child := range scope.Children() { // want "NumChildren/Child loop can simplified using Scope.Children iteration"
first := scope.Child(0) // the name heuristic should not be fooled by this
print(first, child)
}
}
}
func _(union, union2 *types.Union) {
for term := range union.Terms() { // want "Len/Term loop can simplified using Union.Terms iteration"
print(term)
print(term)
}
for i := union.Len() - 1; i >= 0; i-- { // nope: wrong loop form
print(union.Term(i))
}
for i := 0; i <= union.Len(); i++ { // nope: wrong loop form
print(union.Term(i))
}
for i := 0; i <= union.Len(); i++ { // nope: use of i not in x.At(i)
print(i, union.Term(i))
}
for i := 0; i <= union.Len(); i++ { // nope: x.At and x.Len have different receivers
print(i, union2.Term(i))
}
}
func _(tuple *types.Tuple) {
for foo := range tuple.Variables() { // want "Len/At loop can simplified using Tuple.Variables iteration"
if foo := foo; true { // => preferred name = "foo"
print(foo)
}
bar := foo
print(bar)
}
{
// The name v is already declared, but not
// used in the loop, so we can use it again.
v := 1
print(v)
for v := range tuple.Variables() { // want "Len/At loop can simplified using Tuple.Variables iteration"
print(v)
}
}
{
// The name v is used from the loop, so
// we must choose a fresh name.
v := 1
for v0 := range tuple.Variables() { // want "Len/At loop can simplified using Tuple.Variables iteration"
print(v0, v)
}
}
}
func _(t reflect.Type) {
for field := range t.Fields() { // want "NumField/Field loop can simplified using Type.Fields iteration"
print(field)
}
for method := range t.Methods() { // want "NumMethod/Method loop can simplified using Type.Methods iteration"
print(method)
}
for in := range t.Ins() { // want "NumIn/In loop can simplified using Type.Ins iteration"
print(in)
}
for out := range t.Outs() { // want "NumOut/Out loop can simplified using Type.Outs iteration"
print(out)
}
}
func _(v reflect.Value) {
for _, field := range v.Fields() { // want "NumField/Field loop can simplified using Value.Fields iteration"
print(field)
}
// Ideally we would use both parts of Value.Field's iter.Seq2 here.
for i := 0; i < v.NumField(); i++ {
print(v.Field(i), v.Type().Field(i)) // nope
}
for _, method := range v.Methods() { // want "NumMethod/Method loop can simplified using Value.Methods iteration"
print(method)
}
}
================================================
FILE: go/analysis/passes/modernize/testdata/src/stringsbuilder/stringsbuilder.go
================================================
package stringsbuilder
import "strings"
// basic test
func _() {
var s string
s += "before"
for range 10 {
s += "in" // want "using string \\+= string in a loop is inefficient"
s += "in2"
}
s += "after"
print(s)
}
// with initializer
func _() {
var s = "a"
for range 10 {
s += "b" // want "using string \\+= string in a loop is inefficient"
}
print(s)
}
// with empty initializer
func _() {
var s = ""
for range 10 {
s += "b" // want "using string \\+= string in a loop is inefficient"
}
print(s)
}
// with short decl
func _() {
s := "a"
for range 10 {
s += "b" // want "using string \\+= string in a loop is inefficient"
}
print(s)
}
// with short decl and empty initializer
func _() {
s := ""
for range 10 {
s += "b" // want "using string \\+= string in a loop is inefficient"
}
print(s)
}
// nope: += must appear at least once within a loop.
func _() {
var s string
s += "a"
s += "b"
s += "c"
print(s)
}
// nope: the declaration of s is not in a block.
func _() {
if s := "a"; true {
for range 10 {
s += "x"
}
print(s)
}
}
// in a switch (special case of "in a block" logic)
func _() {
switch {
default:
s := "a"
for range 10 {
s += "b" // want "using string \\+= string in a loop is inefficient"
}
print(s)
}
}
// nope: don't handle direct assignments to the string (only +=).
func _(x string) string {
var s string
s = x
for range 3 {
s += "" + x
}
return s
}
// Regression test for bug in a GenDecl with parens.
func issue75318(slice []string) string {
var (
msg string
)
for _, s := range slice {
msg += s // want "using string \\+= string in a loop is inefficient"
}
return msg
}
// Regression test for https://go.dev/issue/76983.
// We offer only the first fix if the second would overlap.
// This is an ad-hoc mitigation of the more general issue #76476.
func _(slice []string) string {
a := "12"
for range 2 {
a += "34" // want "using string \\+= string in a loop is inefficient"
}
b := "56"
for range 2 {
b += "78"
}
a += b
return a
}
func _(slice []string) string {
var a strings.Builder
a.WriteString("12")
for range 2 {
a.WriteString("34")
}
b := "56"
for range 2 {
b += "78" // want "using string \\+= string in a loop is inefficient"
}
a.WriteString(b)
return a.String()
}
// Regression test for go.dev/issue/76934, which mutilated the var decl.
func stringDeclaredWithVarDecl() {
var (
before int // this is ok
str = "hello world"
)
for range 100 {
str += "!" // want "using string \\+= string in a loop is inefficient"
}
println(before, str)
}
func nopeStringIsNotLastValueSpecInVarDecl() {
var (
str = "hello world"
after int // this defeats the transformation
)
for range 100 {
str += "!" // nope
}
println(str, after)
}
// Regression test for go.dev/issue/77659.
// Multiple rvalue uses of the accumulated string should all be converted.
func _() {
acc := "s1"
for _, s := range []string{"foo", "bar"} {
acc += s // want "using string \\+= string in a loop is inefficient"
}
_ = acc
_ = acc + "footer2"
}
// Regression test for go.dev/issue/77659 (negative case).
// += after an rvalue use should still be rejected.
func _() {
var s string
for _, x := range []string{"a", "b"} {
s += x
}
print(s)
s += "extra" // nope: += after rvalue use
print(s)
}
================================================
FILE: go/analysis/passes/modernize/testdata/src/stringsbuilder/stringsbuilder.go.golden
================================================
package stringsbuilder
import "strings"
// basic test
func _() {
var s strings.Builder
s.WriteString("before")
for range 10 {
s.WriteString("in") // want "using string \\+= string in a loop is inefficient"
s.WriteString("in2")
}
s.WriteString("after")
print(s.String())
}
// with initializer
func _() {
var s strings.Builder
s.WriteString("a")
for range 10 {
s.WriteString("b") // want "using string \\+= string in a loop is inefficient"
}
print(s.String())
}
// with empty initializer
func _() {
var s strings.Builder
for range 10 {
s.WriteString("b") // want "using string \\+= string in a loop is inefficient"
}
print(s.String())
}
// with short decl
func _() {
var s strings.Builder
s.WriteString("a")
for range 10 {
s.WriteString("b") // want "using string \\+= string in a loop is inefficient"
}
print(s.String())
}
// with short decl and empty initializer
func _() {
var s strings.Builder
for range 10 {
s.WriteString("b") // want "using string \\+= string in a loop is inefficient"
}
print(s.String())
}
// nope: += must appear at least once within a loop.
func _() {
var s string
s += "a"
s += "b"
s += "c"
print(s)
}
// nope: the declaration of s is not in a block.
func _() {
if s := "a"; true {
for range 10 {
s += "x"
}
print(s)
}
}
// in a switch (special case of "in a block" logic)
func _() {
switch {
default:
var s strings.Builder
s.WriteString("a")
for range 10 {
s.WriteString("b") // want "using string \\+= string in a loop is inefficient"
}
print(s.String())
}
}
// nope: don't handle direct assignments to the string (only +=).
func _(x string) string {
var s string
s = x
for range 3 {
s += "" + x
}
return s
}
// Regression test for bug in a GenDecl with parens.
func issue75318(slice []string) string {
var (
msg strings.Builder
)
for _, s := range slice {
msg.WriteString(s) // want "using string \\+= string in a loop is inefficient"
}
return msg.String()
}
// Regression test for https://go.dev/issue/76983.
// We offer only the first fix if the second would overlap.
// This is an ad-hoc mitigation of the more general issue #76476.
func _(slice []string) string {
var a strings.Builder; a.WriteString("12")
for range 2 {
a.WriteString("34") // want "using string \\+= string in a loop is inefficient"
}
b := "56"
for range 2 {
b += "78"
}
a.WriteString(b)
return a.String()
}
func _(slice []string) string {
var a strings.Builder
a.WriteString("12")
for range 2 {
a.WriteString("34")
}
var b strings.Builder
b.WriteString("56")
for range 2 {
b.WriteString("78") // want "using string \\+= string in a loop is inefficient"
}
a.WriteString(b.String())
return a.String()
}
// Regression test for go.dev/issue/76934, which mutilated the var decl.
func stringDeclaredWithVarDecl() {
var (
before int // this is ok
str strings.Builder
)
str.WriteString("hello world")
for range 100 {
str.WriteString("!") // want "using string \\+= string in a loop is inefficient"
}
println(before, str.String())
}
func nopeStringIsNotLastValueSpecInVarDecl() {
var (
str = "hello world"
after int // this defeats the transformation
)
for range 100 {
str += "!" // nope
}
println(str, after)
}
// Regression test for go.dev/issue/77659.
// Multiple rvalue uses of the accumulated string should all be converted.
func _() {
var acc strings.Builder
acc.WriteString("s1")
for _, s := range []string{"foo", "bar"} {
acc.WriteString(s) // want "using string \\+= string in a loop is inefficient"
}
_ = acc.String()
_ = acc.String() + "footer2"
}
// Regression test for go.dev/issue/77659 (negative case).
// += after an rvalue use should still be rejected.
func _() {
var s string
for _, x := range []string{"a", "b"} {
s += x
}
print(s)
s += "extra" // nope: += after rvalue use
print(s)
}
================================================
FILE: go/analysis/passes/modernize/testdata/src/stringscut/stringscut.go
================================================
package stringscut
import (
"bytes"
"strings"
)
func basic(s string) bool {
s = "reassigned"
i := strings.Index(s, "=") // want "strings.Index can be simplified using strings.Cut"
if i >= 0 {
print(s[:i])
}
return i >= 0
}
func basic_contains(s string) bool {
s = "reassigned"
i := strings.Index(s, "=") // want "strings.Index can be simplified using strings.Contains"
return i >= 0
}
func contains_variety(s, sub string) {
i := strings.Index(s, sub) // want "strings.Index can be simplified using strings.Contains"
if i >= 0 {
print("found")
}
if i < 0 {
print("not found")
}
if i <= -1 {
print("not found")
}
}
func basic_contains_bytes(s string) bool {
i := strings.IndexByte(s, '=') // want "strings.IndexByte can be simplified using strings.Contains"
return i < 0
}
func basic_contains_bytes_byte(s []byte) bool {
i := bytes.IndexByte(s, 22) // want "bytes.IndexByte can be simplified using bytes.Contains"
return i < 0
}
func skip_var_decl(s string) bool {
var i int
i = strings.Index(s, "=") // don't modernize - i might be reassigned
print(s[:i])
return i >= 0
}
func basic_substr_arg(s string, substr string) bool {
i := strings.Index(s, substr) // want "strings.Index can be simplified using strings.Cut"
if i >= 0 {
print(s[i+len(substr):])
}
return i >= 0
}
func wrong_len_arg(s string, substr string) bool {
i := strings.Index(s, substr) // don't modernize since i+len(s) is not valid
print(s[i+len(s):])
return i >= 0
}
func basic_strings_byte(s string) bool {
i := strings.IndexByte(s, '+') // want "strings.IndexByte can be simplified using strings.Cut"
if i >= 0 {
print(s[:i])
}
return i >= 0
}
func basic_strings_byte_int(s string) bool {
i := strings.IndexByte(s, 55) // want "strings.IndexByte can be simplified using strings.Cut"
if i >= 0 {
print(s[:i])
}
return i >= 0
}
func basic_strings_byte_var(s string) bool {
b := byte('b')
i := strings.IndexByte(s, b) // want "strings.IndexByte can be simplified using strings.Cut"
if i >= 0 {
print(s[:i])
}
return i >= 0
}
func basic_bytes(b []byte) []byte {
i := bytes.Index(b, []byte("str")) // want "bytes.Index can be simplified using bytes.Cut"
if i >= 0 {
return b[:i]
} else {
return b[i+3:]
}
}
func basic_index_bytes(b []byte) string {
i := bytes.IndexByte(b, 's') // don't modernize: b[i+1:] in else is not guarded
if i >= 0 {
return string(b[:i])
} else {
return string(b[i+1:])
}
}
func const_substr_len(s string) bool {
i := strings.Index(s, "=") // want "strings.Index can be simplified using strings.Cut"
if i >= 0 {
r := s[i+len("="):]
return len(r) > 0
}
return false
}
func const_for_len(s string) bool {
i := strings.Index(s, "=") // want "strings.Index can be simplified using strings.Cut"
if i >= 0 {
r := s[i+1:]
return len(r) > 0
}
return false
}
func index(s string) bool {
i := strings.Index(s, "=") // want "strings.Index can be simplified using strings.Cut"
if i < 0 {
return false
}
if i >= 0 {
return true
}
print(s[:i])
return true
}
func index_flipped(s string) bool {
i := strings.Index(s, "=") // want "strings.Index can be simplified using strings.Cut"
if 0 > i {
return false
}
if 0 <= i {
return true
}
print(s[:i])
return true
}
func invalid_index(s string) bool {
i := strings.Index(s, "=") // don't modernize since i is used in an "invalid" binaryexpr
if 0 > i {
return false
}
if i < 10 {
return true
}
return true
}
func invalid_slice(s string) string {
i := strings.Index(s, "=") // don't modernize since i is used in an "invalid" slice index
if i >= 0 {
return s[i+4:]
}
return ""
}
func index_and_before_after(s string) string {
substr := "="
i := strings.Index(s, substr) // don't modernize: s[i+len(substr):] and s[len(substr)+i:] not nonneg-guarded
if i == -1 {
print("test")
}
if i < 0 {
return ""
} else {
if i >= 0 {
return s[:i]
} else {
return s[i+len(substr):]
}
}
if -1 == i {
return s[len(substr)+i:]
}
return "final"
}
func idx_var_init(s string) (string, string) {
var idx = strings.Index(s, "=") // don't modernize: s[0:idx] is not guarded
return s[0:idx], s
}
func idx_reassigned(s string) string {
idx := strings.Index(s, "=") // don't modernize since idx gets reassigned
idx = 10
return s[:idx]
}
func idx_printed(s string) string {
idx := strings.Index(s, "=") // don't modernize since idx is used
print(idx)
return s[:idx]
}
func idx_aliased(s string) string {
idx := strings.Index(s, "=") // don't modernize since idx gets aliased
i := idx
return s[:i]
}
func idx_aliased_var(s string) string {
idx := strings.Index(s, "=") // don't modernize since idx gets aliased
var i = idx
print(i)
return s[:idx]
}
func s_modified(s string) string {
idx := strings.Index(s, "=") // don't modernize since s gets modified
s = "newstring"
return s[:idx]
}
func s_modified_no_params() string {
s := "string"
idx := strings.Index(s, "=") // don't modernize since s gets modified
s = "newstring"
return s[:idx]
}
func s_in_func_call() string {
s := "string"
substr := "substr"
idx := strings.Index(s, substr) // don't modernize: s[:idx] is not guarded
function(s)
return s[:idx]
}
func s_pointer() string {
s := "string"
idx := strings.Index(s, "s")
ptr := &s // don't modernize since s may get modified
reference_str(ptr)
return s[:idx]
}
func s_pointer_before_call() string {
s := "string"
ptr := &s // don't modernize since s may get modified
reference_str(ptr)
idx := strings.Index(s, "s")
return s[:idx]
}
func idx_used_before(s string, sub string) string {
var index int
reference_int(&index)
index = strings.Index(s, sub) // don't modernize since index may get modified
blank()
if index >= 0 {
return s[:index]
}
return ""
}
func idx_used_other_substr(s string, sub string) string {
otherstr := "other"
i := strings.Index(s, sub)
print(otherstr[:i]) // don't modernize since i used in another slice expr
if i >= 0 {
return s[:i]
} else {
return ""
}
}
func idx_gtr_zero_invalid(s string, sub string) string {
i := strings.Index(s, sub)
if i > 0 { // don't modernize since this is a stronger claim than i >= 0
return s[:i]
}
return ""
}
func idx_gtreq_one_invalid(s string, sub string) string {
i := strings.Index(s, sub)
if i >= 1 { // don't modernize since this is a stronger claim than i >= 0
return s[:i]
}
return ""
}
func idx_gtr_negone(s string, sub string) string {
i := strings.Index(s, sub) // want "strings.Index can be simplified using strings.Cut"
if i > -1 {
return s[:i]
}
if i != -1 {
return s
}
return ""
}
// Regression test for a crash (https://go.dev/issue/77208)
func idx_call() {
i := bytes.Index(b(), []byte(""))
_ = i
}
// Fix for golang/go#77566
func multipleCallsSameScope(s string) (bool, bool) {
i := strings.Index(s, "=") // want "strings.Index can be simplified using strings.Cut"
if i != -1 {
print(s[:i])
}
j := strings.Index(s, "-") // want "strings.Index can be simplified using strings.Cut"
if j != -1 {
print(s[:j])
}
return i >= 0, j >= 0
}
func shadowing(s string) string {
ok := "true"
before := "before"
print(before) // declared within scope but not used after the index call, so no fresh name
i := strings.Index(s, "=") // want "strings.Index can be simplified using strings.Cut"
print(ok)
if i >= 0 {
print(s[:i])
print(i >= 0)
return s[i+1:]
}
return ""
}
func foreshadowing(s string) string {
i := strings.Index(s, "=") // want "strings.Index can be simplified using strings.Cut"
if i != -1 {
return s[i+1:]
}
// Generate a fresh name for ok because it is used within the scope after the Index call.
ok, m := true, "m"
if ok {
return m
}
if i != -1 {
return s
}
return ""
}
func b() []byte {
return nil
}
func function(s string) {}
func reference_str(s *string) {}
func reference_int(i *int) {}
func blank() {}
// Regression test for unguarded slice uses (https://go.dev/issue/77737).
// The s[colon+1:] usage outside the "if" relies on -1+1=0 to return the full
// string when the separator is absent. Rewriting to "after" would return "".
func unguarded_after_slice(s string) (int, string) {
colon := strings.Index(s, ":")
if colon != -1 {
print(s[:colon])
}
return colon, s[colon+1:] // don't modernize: s[colon+1:] not guarded
}
// Same as above but with the guard using i < 0.
func unguarded_after_slice_negcheck(s string) string {
i := strings.Index(s, ":")
if i < 0 {
print("not found")
}
return s[i+1:] // don't modernize: s[i+1:] not guarded
}
// Safe: both slice uses are inside the nonneg guard.
func guarded_both_slices(s string) (string, string) {
i := strings.Index(s, ":") // want "strings.Index can be simplified using strings.Cut"
if i >= 0 {
return s[:i], s[i+1:]
}
return "", s
}
// Safe: slice uses after early-return negative check.
func guarded_early_return(s string) (string, string) {
i := strings.Index(s, ":") // want "strings.Index can be simplified using strings.Cut"
if i < 0 {
return "", s
}
return s[:i], s[i+1:]
}
// Safe: slice uses in else of negative check.
func guarded_neg_else(s string) (string, string) {
i := strings.Index(s, ":") // want "strings.Index can be simplified using strings.Cut"
if i == -1 {
return "", s
} else {
return s[:i], s[i+1:]
}
}
================================================
FILE: go/analysis/passes/modernize/testdata/src/stringscut/stringscut.go.golden
================================================
package stringscut
import (
"bytes"
"strings"
)
func basic(s string) bool {
s = "reassigned"
before, _, ok := strings.Cut(s, "=") // want "strings.Index can be simplified using strings.Cut"
if ok {
print(before)
}
return ok
}
func basic_contains(s string) bool {
s = "reassigned"
found := strings.Contains(s, "=") // want "strings.Index can be simplified using strings.Contains"
return found
}
func contains_variety(s, sub string) {
found := strings.Contains(s, sub) // want "strings.Index can be simplified using strings.Contains"
if found {
print("found")
}
if !found {
print("not found")
}
if !found {
print("not found")
}
}
func basic_contains_bytes(s string) bool {
found := strings.Contains(s, "=") // want "strings.IndexByte can be simplified using strings.Contains"
return !found
}
func basic_contains_bytes_byte(s []byte) bool {
found := bytes.Contains(s, []byte{22}) // want "bytes.IndexByte can be simplified using bytes.Contains"
return !found
}
func skip_var_decl(s string) bool {
var i int
i = strings.Index(s, "=") // don't modernize - i might be reassigned
print(s[:i])
return i >= 0
}
func basic_substr_arg(s string, substr string) bool {
_, after, ok := strings.Cut(s, substr) // want "strings.Index can be simplified using strings.Cut"
if ok {
print(after)
}
return ok
}
func wrong_len_arg(s string, substr string) bool {
i := strings.Index(s, substr) // don't modernize since i+len(s) is not valid
print(s[i+len(s):])
return i >= 0
}
func basic_strings_byte(s string) bool {
before, _, ok := strings.Cut(s, "+") // want "strings.IndexByte can be simplified using strings.Cut"
if ok {
print(before)
}
return ok
}
func basic_strings_byte_int(s string) bool {
before, _, ok := strings.Cut(s, "7") // want "strings.IndexByte can be simplified using strings.Cut"
if ok {
print(before)
}
return ok
}
func basic_strings_byte_var(s string) bool {
b := byte('b')
before, _, ok := strings.Cut(s, string(b)) // want "strings.IndexByte can be simplified using strings.Cut"
if ok {
print(before)
}
return ok
}
func basic_bytes(b []byte) []byte {
before, after, ok := bytes.Cut(b, []byte("str")) // want "bytes.Index can be simplified using bytes.Cut"
if ok {
return before
} else {
return after
}
}
func basic_index_bytes(b []byte) string {
i := bytes.IndexByte(b, 's') // don't modernize: b[i+1:] in else is not guarded
if i >= 0 {
return string(b[:i])
} else {
return string(b[i+1:])
}
}
func const_substr_len(s string) bool {
_, after, ok := strings.Cut(s, "=") // want "strings.Index can be simplified using strings.Cut"
if ok {
r := after
return len(r) > 0
}
return false
}
func const_for_len(s string) bool {
_, after, ok := strings.Cut(s, "=") // want "strings.Index can be simplified using strings.Cut"
if ok {
r := after
return len(r) > 0
}
return false
}
func index(s string) bool {
before, _, ok := strings.Cut(s, "=") // want "strings.Index can be simplified using strings.Cut"
if !ok {
return false
}
if ok {
return true
}
print(before)
return true
}
func index_flipped(s string) bool {
before, _, ok := strings.Cut(s, "=") // want "strings.Index can be simplified using strings.Cut"
if !ok {
return false
}
if ok {
return true
}
print(before)
return true
}
func invalid_index(s string) bool {
i := strings.Index(s, "=") // don't modernize since i is used in an "invalid" binaryexpr
if 0 > i {
return false
}
if i < 10 {
return true
}
return true
}
func invalid_slice(s string) string {
i := strings.Index(s, "=") // don't modernize since i is used in an "invalid" slice index
if i >= 0 {
return s[i+4:]
}
return ""
}
func index_and_before_after(s string) string {
substr := "="
i := strings.Index(s, substr) // don't modernize: s[i+len(substr):] and s[len(substr)+i:] not nonneg-guarded
if i == -1 {
print("test")
}
if i < 0 {
return ""
} else {
if i >= 0 {
return s[:i]
} else {
return s[i+len(substr):]
}
}
if -1 == i {
return s[len(substr)+i:]
}
return "final"
}
func idx_var_init(s string) (string, string) {
var idx = strings.Index(s, "=") // don't modernize: s[0:idx] is not guarded
return s[0:idx], s
}
func idx_reassigned(s string) string {
idx := strings.Index(s, "=") // don't modernize since idx gets reassigned
idx = 10
return s[:idx]
}
func idx_printed(s string) string {
idx := strings.Index(s, "=") // don't modernize since idx is used
print(idx)
return s[:idx]
}
func idx_aliased(s string) string {
idx := strings.Index(s, "=") // don't modernize since idx gets aliased
i := idx
return s[:i]
}
func idx_aliased_var(s string) string {
idx := strings.Index(s, "=") // don't modernize since idx gets aliased
var i = idx
print(i)
return s[:idx]
}
func s_modified(s string) string {
idx := strings.Index(s, "=") // don't modernize since s gets modified
s = "newstring"
return s[:idx]
}
func s_modified_no_params() string {
s := "string"
idx := strings.Index(s, "=") // don't modernize since s gets modified
s = "newstring"
return s[:idx]
}
func s_in_func_call() string {
s := "string"
substr := "substr"
idx := strings.Index(s, substr) // don't modernize: s[:idx] is not guarded
function(s)
return s[:idx]
}
func s_pointer() string {
s := "string"
idx := strings.Index(s, "s")
ptr := &s // don't modernize since s may get modified
reference_str(ptr)
return s[:idx]
}
func s_pointer_before_call() string {
s := "string"
ptr := &s // don't modernize since s may get modified
reference_str(ptr)
idx := strings.Index(s, "s")
return s[:idx]
}
func idx_used_before(s string, sub string) string {
var index int
reference_int(&index)
index = strings.Index(s, sub) // don't modernize since index may get modified
blank()
if index >= 0 {
return s[:index]
}
return ""
}
func idx_used_other_substr(s string, sub string) string {
otherstr := "other"
i := strings.Index(s, sub)
print(otherstr[:i]) // don't modernize since i used in another slice expr
if i >= 0 {
return s[:i]
} else {
return ""
}
}
func idx_gtr_zero_invalid(s string, sub string) string {
i := strings.Index(s, sub)
if i > 0 { // don't modernize since this is a stronger claim than i >= 0
return s[:i]
}
return ""
}
func idx_gtreq_one_invalid(s string, sub string) string {
i := strings.Index(s, sub)
if i >= 1 { // don't modernize since this is a stronger claim than i >= 0
return s[:i]
}
return ""
}
func idx_gtr_negone(s string, sub string) string {
before, _, ok := strings.Cut(s, sub) // want "strings.Index can be simplified using strings.Cut"
if ok {
return before
}
if ok {
return s
}
return ""
}
// Regression test for a crash (https://go.dev/issue/77208)
func idx_call() {
i := bytes.Index(b(), []byte(""))
_ = i
}
// Fix for golang/go#77566
func multipleCallsSameScope(s string) (bool, bool) {
before, _, ok := strings.Cut(s, "=") // want "strings.Index can be simplified using strings.Cut"
if ok {
print(before)
}
before0, _, ok0 := strings.Cut(s, "-") // want "strings.Index can be simplified using strings.Cut"
if ok0 {
print(before0)
}
return ok, ok0
}
func shadowing(s string) string {
ok := "true"
before := "before"
print(before) // declared within scope but not used after the index call, so no fresh name
before, after, ok0 := strings.Cut(s, "=") // want "strings.Index can be simplified using strings.Cut"
print(ok)
if ok0 {
print(before)
print(ok0)
return after
}
return ""
}
func foreshadowing(s string) string {
_, after, ok0 := strings.Cut(s, "=") // want "strings.Index can be simplified using strings.Cut"
if ok0 {
return after
}
// Generate a fresh name for ok because it is used within the scope after the Index call.
ok, m := true, "m"
if ok {
return m
}
if ok0 {
return s
}
return ""
}
func b() []byte {
return nil
}
func function(s string) {}
func reference_str(s *string) {}
func reference_int(i *int) {}
func blank() {}
// Regression test for unguarded slice uses (https://go.dev/issue/77737).
// The s[colon+1:] usage outside the "if" relies on -1+1=0 to return the full
// string when the separator is absent. Rewriting to "after" would return "".
func unguarded_after_slice(s string) (int, string) {
colon := strings.Index(s, ":")
if colon != -1 {
print(s[:colon])
}
return colon, s[colon+1:] // don't modernize: s[colon+1:] not guarded
}
// Same as above but with the guard using i < 0.
func unguarded_after_slice_negcheck(s string) string {
i := strings.Index(s, ":")
if i < 0 {
print("not found")
}
return s[i+1:] // don't modernize: s[i+1:] not guarded
}
// Safe: both slice uses are inside the nonneg guard.
func guarded_both_slices(s string) (string, string) {
before, after, ok := strings.Cut(s, ":") // want "strings.Index can be simplified using strings.Cut"
if ok {
return before, after
}
return "", s
}
// Safe: slice uses after early-return negative check.
func guarded_early_return(s string) (string, string) {
before, after, ok := strings.Cut(s, ":") // want "strings.Index can be simplified using strings.Cut"
if !ok {
return "", s
}
return before, after
}
// Safe: slice uses in else of negative check.
func guarded_neg_else(s string) (string, string) {
before, after, ok := strings.Cut(s, ":") // want "strings.Index can be simplified using strings.Cut"
if !ok {
return "", s
} else {
return before, after
}
}
================================================
FILE: go/analysis/passes/modernize/testdata/src/stringscutprefix/bytescutprefix/bytescutprefix.go
================================================
package bytescutprefix
import (
"bytes"
)
func _() {
if bytes.HasPrefix(bss, bspre) { // want "HasPrefix \\+ TrimPrefix can be simplified to CutPrefix"
a := bytes.TrimPrefix(bss, bspre)
_ = a
}
if bytes.HasPrefix([]byte(""), []byte("")) { // want "HasPrefix \\+ TrimPrefix can be simplified to CutPrefix"
a := bytes.TrimPrefix([]byte(""), []byte(""))
_ = a
}
if bytes.HasSuffix(bss, bssuf) { // want "HasSuffix \\+ TrimSuffix can be simplified to CutSuffix"
a := bytes.TrimSuffix(bss, bssuf)
_ = a
}
if bytes.HasSuffix([]byte(""), []byte("")) { // want "HasSuffix \\+ TrimSuffix can be simplified to CutSuffix"
a := bytes.TrimSuffix([]byte(""), []byte(""))
_ = a
}
}
================================================
FILE: go/analysis/passes/modernize/testdata/src/stringscutprefix/bytescutprefix/bytescutprefix.go.golden
================================================
package bytescutprefix
import (
"bytes"
)
func _() {
if after, ok := bytes.CutPrefix(bss, bspre); ok { // want "HasPrefix \\+ TrimPrefix can be simplified to CutPrefix"
a := after
_ = a
}
if after, ok := bytes.CutPrefix([]byte(""), []byte("")); ok { // want "HasPrefix \\+ TrimPrefix can be simplified to CutPrefix"
a := after
_ = a
}
if before, ok := bytes.CutSuffix(bss, bssuf); ok { // want "HasSuffix \\+ TrimSuffix can be simplified to CutSuffix"
a := before
_ = a
}
if before, ok := bytes.CutSuffix([]byte(""), []byte("")); ok { // want "HasSuffix \\+ TrimSuffix can be simplified to CutSuffix"
a := before
_ = a
}
}
================================================
FILE: go/analysis/passes/modernize/testdata/src/stringscutprefix/bytescutprefix/bytescutprefix_dot.go
================================================
package bytescutprefix
import (
. "bytes"
)
var bss, bspre, bssuf []byte
// test supported cases of pattern 1
func _() {
if HasPrefix(bss, bspre) { // want "HasPrefix \\+ TrimPrefix can be simplified to CutPrefix"
a := TrimPrefix(bss, bspre)
_ = a
}
if HasSuffix(bss, bssuf) { // want "HasSuffix \\+ TrimSuffix can be simplified to CutSuffix"
b := TrimSuffix(bss, bssuf)
_ = b
}
}
================================================
FILE: go/analysis/passes/modernize/testdata/src/stringscutprefix/bytescutprefix/bytescutprefix_dot.go.golden
================================================
package bytescutprefix
import (
. "bytes"
)
var bss, bspre, bssuf []byte
// test supported cases of pattern 1
func _() {
if after, ok := CutPrefix(bss, bspre); ok { // want "HasPrefix \\+ TrimPrefix can be simplified to CutPrefix"
a := after
_ = a
}
if before, ok := CutSuffix(bss, bssuf); ok { // want "HasSuffix \\+ TrimSuffix can be simplified to CutSuffix"
b := before
_ = b
}
}
================================================
FILE: go/analysis/passes/modernize/testdata/src/stringscutprefix/stringscutprefix.go
================================================
package stringscutprefix
import (
"strings"
)
var (
s, pre, suf string
)
// test supported cases of pattern 1 - CutPrefix
func _() {
if strings.HasPrefix(s, pre) { // want "HasPrefix \\+ TrimPrefix can be simplified to CutPrefix"
a := strings.TrimPrefix(s, pre)
_ = a
}
if strings.HasPrefix("", "") { // want "HasPrefix \\+ TrimPrefix can be simplified to CutPrefix"
a := strings.TrimPrefix("", "")
_ = a
}
if strings.HasPrefix(s, "") { // want "HasPrefix \\+ TrimPrefix can be simplified to CutPrefix"
println([]byte(strings.TrimPrefix(s, "")))
}
if strings.HasPrefix(s, "") { // want "HasPrefix \\+ TrimPrefix can be simplified to CutPrefix"
a, b := "", strings.TrimPrefix(s, "")
_, _ = a, b
}
if strings.HasPrefix(s, "") { // want "HasPrefix \\+ TrimPrefix can be simplified to CutPrefix"
a, b := strings.TrimPrefix(s, ""), strings.TrimPrefix(s, "") // only replace the first occurrence
s = "123"
b = strings.TrimPrefix(s, "") // only replace the first occurrence
_, _ = a, b
}
var a, b string
if strings.HasPrefix(s, "") { // want "HasPrefix \\+ TrimPrefix can be simplified to CutPrefix"
a, b = "", strings.TrimPrefix(s, "")
_, _ = a, b
}
}
// test basic cases for CutSuffix - only covering the key differences
func _() {
if strings.HasSuffix(s, suf) { // want "HasSuffix \\+ TrimSuffix can be simplified to CutSuffix"
a := strings.TrimSuffix(s, suf)
_ = a
}
if strings.HasSuffix(s, "") { // want "HasSuffix \\+ TrimSuffix can be simplified to CutSuffix"
println([]byte(strings.TrimSuffix(s, "")))
}
}
// test cases that are not supported by pattern1 - CutPrefix
func _() {
ok := strings.HasPrefix("", "")
if ok { // noop, currently it doesn't track the result usage of HasPrefix
a := strings.TrimPrefix("", "")
_ = a
}
if strings.HasPrefix(s, pre) {
a := strings.TrimPrefix("", "") // noop, as the argument isn't the same
_ = a
}
if strings.HasPrefix(s, pre) {
var result string
result = strings.TrimPrefix("", "") // noop, as we believe define is more popular.
_ = result
}
if strings.HasPrefix("", "") {
a := strings.TrimPrefix(s, pre) // noop, as the argument isn't the same
_ = a
}
if s1 := s; strings.HasPrefix(s1, pre) {
a := strings.TrimPrefix(s1, pre) // noop, as IfStmt.Init is present
_ = a
}
}
// test basic unsupported case for CutSuffix
func _() {
if strings.HasSuffix(s, suf) {
a := strings.TrimSuffix("", "") // noop, as the argument isn't the same
_ = a
}
}
var value0 string
// test supported cases of pattern2 - CutPrefix
func _() {
if after := strings.TrimPrefix(s, pre); after != s { // want "TrimPrefix can be simplified to CutPrefix"
println(after)
}
if after := strings.TrimPrefix(s, pre); s != after { // want "TrimPrefix can be simplified to CutPrefix"
println(after)
}
if after := strings.TrimPrefix(s, pre); s != after { // want "TrimPrefix can be simplified to CutPrefix"
println(strings.TrimPrefix(s, pre)) // noop here
}
if after := strings.TrimPrefix(s, ""); s != after { // want "TrimPrefix can be simplified to CutPrefix"
println(after)
}
var ok bool // define an ok variable to test the fix won't shadow it for its if stmt body
if after := strings.TrimPrefix(s, pre); after != s { // want "TrimPrefix can be simplified to CutPrefix"
_ = ok
println(after)
}
_ = ok // fine to shadow, since ok is not used within the ifstmt block
if after := strings.TrimPrefix(s, pre); after != s { // want "TrimPrefix can be simplified to CutPrefix"
println(after)
}
var predefined string
if predefined = strings.TrimPrefix(s, pre); s != predefined { // noop
println(predefined)
}
if predefined = strings.TrimPrefix(s, pre); s != predefined { // noop
println(&predefined)
}
var value string
if value = strings.TrimPrefix(s, pre); s != value { // noop
println(value)
}
lhsMap := make(map[string]string)
if lhsMap[""] = strings.TrimPrefix(s, pre); s != lhsMap[""] { // noop
println(lhsMap[""])
}
arr := make([]string, 0)
if arr[0] = strings.TrimPrefix(s, pre); s != arr[0] { // noop
println(arr[0])
}
type example struct {
field string
}
var e example
if e.field = strings.TrimPrefix(s, pre); s != e.field { // noop
println(e.field)
}
}
// test basic cases for pattern2 - CutSuffix
func _() {
if before := strings.TrimSuffix(s, suf); before != s { // want "TrimSuffix can be simplified to CutSuffix"
println(before)
}
if before := strings.TrimSuffix(s, suf); s != before { // want "TrimSuffix can be simplified to CutSuffix"
println(before)
}
}
// test cases that not supported by pattern2 - CutPrefix
func _() {
if after := strings.TrimPrefix(s, pre); s != pre { // noop
println(after)
}
if after := strings.TrimPrefix(s, pre); after != pre { // noop
println(after)
}
if strings.TrimPrefix(s, pre) != s {
println(strings.TrimPrefix(s, pre))
}
}
// test basic unsupported case for pattern2 - CutSuffix
func _() {
if before := strings.TrimSuffix(s, suf); s != suf { // noop
println(before)
}
}
================================================
FILE: go/analysis/passes/modernize/testdata/src/stringscutprefix/stringscutprefix.go.golden
================================================
package stringscutprefix
import (
"strings"
)
var (
s, pre, suf string
)
// test supported cases of pattern 1 - CutPrefix
func _() {
if after, ok := strings.CutPrefix(s, pre); ok { // want "HasPrefix \\+ TrimPrefix can be simplified to CutPrefix"
a := after
_ = a
}
if after, ok := strings.CutPrefix("", ""); ok { // want "HasPrefix \\+ TrimPrefix can be simplified to CutPrefix"
a := after
_ = a
}
if after, ok := strings.CutPrefix(s, ""); ok { // want "HasPrefix \\+ TrimPrefix can be simplified to CutPrefix"
println([]byte(after))
}
if after, ok := strings.CutPrefix(s, ""); ok { // want "HasPrefix \\+ TrimPrefix can be simplified to CutPrefix"
a, b := "", after
_, _ = a, b
}
if after, ok := strings.CutPrefix(s, ""); ok { // want "HasPrefix \\+ TrimPrefix can be simplified to CutPrefix"
a, b := after, strings.TrimPrefix(s, "") // only replace the first occurrence
s = "123"
b = strings.TrimPrefix(s, "") // only replace the first occurrence
_, _ = a, b
}
var a, b string
if after, ok := strings.CutPrefix(s, ""); ok { // want "HasPrefix \\+ TrimPrefix can be simplified to CutPrefix"
a, b = "", after
_, _ = a, b
}
}
// test basic cases for CutSuffix - only covering the key differences
func _() {
if before, ok := strings.CutSuffix(s, suf); ok { // want "HasSuffix \\+ TrimSuffix can be simplified to CutSuffix"
a := before
_ = a
}
if before, ok := strings.CutSuffix(s, ""); ok { // want "HasSuffix \\+ TrimSuffix can be simplified to CutSuffix"
println([]byte(before))
}
}
// test cases that are not supported by pattern1 - CutPrefix
func _() {
ok := strings.HasPrefix("", "")
if ok { // noop, currently it doesn't track the result usage of HasPrefix
a := strings.TrimPrefix("", "")
_ = a
}
if strings.HasPrefix(s, pre) {
a := strings.TrimPrefix("", "") // noop, as the argument isn't the same
_ = a
}
if strings.HasPrefix(s, pre) {
var result string
result = strings.TrimPrefix("", "") // noop, as we believe define is more popular.
_ = result
}
if strings.HasPrefix("", "") {
a := strings.TrimPrefix(s, pre) // noop, as the argument isn't the same
_ = a
}
if s1 := s; strings.HasPrefix(s1, pre) {
a := strings.TrimPrefix(s1, pre) // noop, as IfStmt.Init is present
_ = a
}
}
// test basic unsupported case for CutSuffix
func _() {
if strings.HasSuffix(s, suf) {
a := strings.TrimSuffix("", "") // noop, as the argument isn't the same
_ = a
}
}
var value0 string
// test supported cases of pattern2 - CutPrefix
func _() {
if after, ok := strings.CutPrefix(s, pre); ok { // want "TrimPrefix can be simplified to CutPrefix"
println(after)
}
if after, ok := strings.CutPrefix(s, pre); ok { // want "TrimPrefix can be simplified to CutPrefix"
println(after)
}
if after, ok := strings.CutPrefix(s, pre); ok { // want "TrimPrefix can be simplified to CutPrefix"
println(strings.TrimPrefix(s, pre)) // noop here
}
if after, ok := strings.CutPrefix(s, ""); ok { // want "TrimPrefix can be simplified to CutPrefix"
println(after)
}
var ok bool // define an ok variable to test the fix won't shadow it for its if stmt body
if after, ok0 := strings.CutPrefix(s, pre); ok0 { // want "TrimPrefix can be simplified to CutPrefix"
_ = ok
println(after)
}
_ = ok // fine to shadow, since ok is not used within the ifstmt block
if after, ok := strings.CutPrefix(s, pre); ok { // want "TrimPrefix can be simplified to CutPrefix"
println(after)
}
var predefined string
if predefined = strings.TrimPrefix(s, pre); s != predefined { // noop
println(predefined)
}
if predefined = strings.TrimPrefix(s, pre); s != predefined { // noop
println(&predefined)
}
var value string
if value = strings.TrimPrefix(s, pre); s != value { // noop
println(value)
}
lhsMap := make(map[string]string)
if lhsMap[""] = strings.TrimPrefix(s, pre); s != lhsMap[""] { // noop
println(lhsMap[""])
}
arr := make([]string, 0)
if arr[0] = strings.TrimPrefix(s, pre); s != arr[0] { // noop
println(arr[0])
}
type example struct {
field string
}
var e example
if e.field = strings.TrimPrefix(s, pre); s != e.field { // noop
println(e.field)
}
}
// test basic cases for pattern2 - CutSuffix
func _() {
if before, ok := strings.CutSuffix(s, suf); ok { // want "TrimSuffix can be simplified to CutSuffix"
println(before)
}
if before, ok := strings.CutSuffix(s, suf); ok { // want "TrimSuffix can be simplified to CutSuffix"
println(before)
}
}
// test cases that not supported by pattern2 - CutPrefix
func _() {
if after := strings.TrimPrefix(s, pre); s != pre { // noop
println(after)
}
if after := strings.TrimPrefix(s, pre); after != pre { // noop
println(after)
}
if strings.TrimPrefix(s, pre) != s {
println(strings.TrimPrefix(s, pre))
}
}
// test basic unsupported case for pattern2 - CutSuffix
func _() {
if before := strings.TrimSuffix(s, suf); s != suf { // noop
println(before)
}
}
================================================
FILE: go/analysis/passes/modernize/testdata/src/stringscutprefix/stringscutprefix_dot.go
================================================
package stringscutprefix
import (
. "strings"
)
// test supported cases of pattern 1 - CutPrefix
func _() {
if HasPrefix(s, pre) { // want "HasPrefix \\+ TrimPrefix can be simplified to CutPrefix"
a := TrimPrefix(s, pre)
_ = a
}
}
// test supported cases of pattern 1 - CutSuffix
func _() {
if HasSuffix(s, suf) { // want "HasSuffix \\+ TrimSuffix can be simplified to CutSuffix"
a := TrimSuffix(s, suf)
_ = a
}
}
// test supported cases of pattern2 - CutPrefix
func _() {
if after := TrimPrefix(s, pre); after != s { // want "TrimPrefix can be simplified to CutPrefix"
println(after)
}
if after := TrimPrefix(s, pre); s != after { // want "TrimPrefix can be simplified to CutPrefix"
println(after)
}
}
// test supported cases of pattern2 - CutSuffix
func _() {
if before := TrimSuffix(s, suf); before != s { // want "TrimSuffix can be simplified to CutSuffix"
println(before)
}
if before := TrimSuffix(s, suf); s != before { // want "TrimSuffix can be simplified to CutSuffix"
println(before)
}
}
================================================
FILE: go/analysis/passes/modernize/testdata/src/stringscutprefix/stringscutprefix_dot.go.golden
================================================
package stringscutprefix
import (
. "strings"
)
// test supported cases of pattern 1 - CutPrefix
func _() {
if after, ok := CutPrefix(s, pre); ok { // want "HasPrefix \\+ TrimPrefix can be simplified to CutPrefix"
a := after
_ = a
}
}
// test supported cases of pattern 1 - CutSuffix
func _() {
if before, ok := CutSuffix(s, suf); ok { // want "HasSuffix \\+ TrimSuffix can be simplified to CutSuffix"
a := before
_ = a
}
}
// test supported cases of pattern2 - CutPrefix
func _() {
if after, ok := CutPrefix(s, pre); ok { // want "TrimPrefix can be simplified to CutPrefix"
println(after)
}
if after, ok := CutPrefix(s, pre); ok { // want "TrimPrefix can be simplified to CutPrefix"
println(after)
}
}
// test supported cases of pattern2 - CutSuffix
func _() {
if before, ok := CutSuffix(s, suf); ok { // want "TrimSuffix can be simplified to CutSuffix"
println(before)
}
if before, ok := CutSuffix(s, suf); ok { // want "TrimSuffix can be simplified to CutSuffix"
println(before)
}
}
================================================
FILE: go/analysis/passes/modernize/testdata/src/testingcontext/testingcontext.go
================================================
package testingcontext
================================================
FILE: go/analysis/passes/modernize/testdata/src/testingcontext/testingcontext_test.go
================================================
package testingcontext
import (
"context"
"testing"
)
func Test(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background()) // want "context.WithCancel can be modernized using t.Context"
defer cancel()
_ = ctx
func() {
ctx, cancel := context.WithCancel(context.Background()) // Nope. scope of defer is not the testing func.
defer cancel()
_ = ctx
}()
{
ctx, cancel := context.WithCancel(context.TODO()) // want "context.WithCancel can be modernized using t.Context"
defer cancel()
_ = ctx
var t int // not in scope of the call to WithCancel
_ = t
}
{
ctx := context.Background()
ctx, cancel := context.WithCancel(context.Background()) // Nope. ctx is redeclared.
defer cancel()
_ = ctx
}
{
var t int
ctx, cancel := context.WithCancel(context.Background()) // Nope. t is shadowed.
defer cancel()
_ = ctx
_ = t
}
t.Run("subtest", func(t2 *testing.T) {
ctx, cancel := context.WithCancel(context.Background()) // want "context.WithCancel can be modernized using t2.Context"
defer cancel()
_ = ctx
})
}
func TestAlt(t2 *testing.T) {
ctx, cancel := context.WithCancel(context.Background()) // want "context.WithCancel can be modernized using t2.Context"
defer cancel()
_ = ctx
}
func Testnot(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background()) // Nope. Not a test func.
defer cancel()
_ = ctx
}
func Benchmark(b *testing.B) {
ctx, cancel := context.WithCancel(context.Background()) // want "context.WithCancel can be modernized using b.Context"
defer cancel()
_ = ctx
b.Run("subtest", func(b2 *testing.B) {
ctx, cancel := context.WithCancel(context.Background()) // want "context.WithCancel can be modernized using b2.Context"
defer cancel()
_ = ctx
})
}
func Fuzz(f *testing.F) {
ctx, cancel := context.WithCancel(context.Background()) // want "context.WithCancel can be modernized using f.Context"
defer cancel()
_ = ctx
}
================================================
FILE: go/analysis/passes/modernize/testdata/src/testingcontext/testingcontext_test.go.golden
================================================
package testingcontext
import (
"context"
"testing"
)
func Test(t *testing.T) {
ctx := t.Context()
_ = ctx
func() {
ctx, cancel := context.WithCancel(context.Background()) // Nope. scope of defer is not the testing func.
defer cancel()
_ = ctx
}()
{
ctx := t.Context()
_ = ctx
var t int // not in scope of the call to WithCancel
_ = t
}
{
ctx := context.Background()
ctx, cancel := context.WithCancel(context.Background()) // Nope. ctx is redeclared.
defer cancel()
_ = ctx
}
{
var t int
ctx, cancel := context.WithCancel(context.Background()) // Nope. t is shadowed.
defer cancel()
_ = ctx
_ = t
}
t.Run("subtest", func(t2 *testing.T) {
ctx := t2.Context()
_ = ctx
})
}
func TestAlt(t2 *testing.T) {
ctx := t2.Context()
_ = ctx
}
func Testnot(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background()) // Nope. Not a test func.
defer cancel()
_ = ctx
}
func Benchmark(b *testing.B) {
ctx := b.Context()
_ = ctx
b.Run("subtest", func(b2 *testing.B) {
ctx := b2.Context()
_ = ctx
})
}
func Fuzz(f *testing.F) {
ctx := f.Context()
_ = ctx
}
================================================
FILE: go/analysis/passes/modernize/testdata/src/unsafefuncs/unsafefuncs.go
================================================
package unsafefuncs
import "unsafe"
func _(ptr unsafe.Pointer) unsafe.Pointer {
return unsafe.Pointer(uintptr(ptr) + 1) // want `pointer \+ integer can be simplified using unsafe.Add`
}
type uP = unsafe.Pointer
func _(ptr uP) uP {
return uP(uintptr(ptr) + 1) // want `pointer \+ integer can be simplified using unsafe.Add`
}
func _(ptr unsafe.Pointer, n int) unsafe.Pointer {
return unsafe.Pointer(uintptr(ptr) + uintptr(n)) // want `pointer \+ integer can be simplified using unsafe.Add`
}
func _(ptr *byte, len int) *byte {
return (*byte)(unsafe.Pointer(uintptr(unsafe.Pointer(ptr)) + uintptr(len))) // want `pointer \+ integer can be simplified using unsafe.Add`
}
type namedUP unsafe.Pointer
func _(ptr namedUP) namedUP {
return namedUP(uintptr(ptr) + 1) // nope: Add does not accept named unsafe.Pointer types
}
================================================
FILE: go/analysis/passes/modernize/testdata/src/unsafefuncs/unsafefuncs.go.golden
================================================
package unsafefuncs
import "unsafe"
func _(ptr unsafe.Pointer) unsafe.Pointer {
return unsafe.Add(ptr, 1) // want `pointer \+ integer can be simplified using unsafe.Add`
}
type uP = unsafe.Pointer
func _(ptr uP) uP {
return unsafe.Add(ptr, 1) // want `pointer \+ integer can be simplified using unsafe.Add`
}
func _(ptr unsafe.Pointer, n int) unsafe.Pointer {
return unsafe.Add(ptr, n) // want `pointer \+ integer can be simplified using unsafe.Add`
}
func _(ptr *byte, len int) *byte {
return (*byte)(unsafe.Add(unsafe.Pointer(ptr), len)) // want `pointer \+ integer can be simplified using unsafe.Add`
}
type namedUP unsafe.Pointer
func _(ptr namedUP) namedUP {
return namedUP(uintptr(ptr) + 1) // nope: Add does not accept named unsafe.Pointer types
}
================================================
FILE: go/analysis/passes/modernize/testdata/src/waitgroupgo/waitgroup.go
================================================
package waitgroup
import (
"fmt"
"sync"
)
// supported case for pattern 1.
func _() {
var wg sync.WaitGroup
wg.Add(1)
go func() { // want "Goroutine creation can be simplified using WaitGroup.Go"
defer wg.Done()
fmt.Println()
}()
wg.Add(1)
go func() { // want "Goroutine creation can be simplified using WaitGroup.Go"
defer wg.Done()
}()
for range 10 {
wg.Add(1)
go func() { // want "Goroutine creation can be simplified using WaitGroup.Go"
defer wg.Done()
fmt.Println()
}()
}
}
// supported case for pattern 2.
func _() {
var wg sync.WaitGroup
wg.Add(1)
go func() { // want "Goroutine creation can be simplified using WaitGroup.Go"
fmt.Println()
wg.Done()
}()
wg.Add(1)
go func() { // want "Goroutine creation can be simplified using WaitGroup.Go"
wg.Done()
}()
for range 10 {
wg.Add(1)
go func() { // want "Goroutine creation can be simplified using WaitGroup.Go"
fmt.Println()
wg.Done()
}()
}
}
// this function puts some wrong usages but waitgroupgo modernizer will still offer fixes.
func _() {
var wg sync.WaitGroup
wg.Add(1)
go func() { // want "Goroutine creation can be simplified using WaitGroup.Go"
defer wg.Done()
defer wg.Done()
fmt.Println()
}()
wg.Add(1)
go func() { // want "Goroutine creation can be simplified using WaitGroup.Go"
defer wg.Done()
fmt.Println()
wg.Done()
}()
wg.Add(1)
go func() { // want "Goroutine creation can be simplified using WaitGroup.Go"
fmt.Println()
wg.Done()
wg.Done()
}()
}
// this function puts the unsupported cases of pattern 1.
func _() {
var wg sync.WaitGroup
wg.Add(1)
go func() {}()
wg.Add(1)
go func(i int) {
defer wg.Done()
fmt.Println(i)
}(1)
wg.Add(1)
go func() {
fmt.Println()
defer wg.Done()
}()
wg.Add(1)
go func() { // noop: no wg.Done call inside function body.
fmt.Println()
}()
go func() { // noop: no Add call before this go stmt.
defer wg.Done()
fmt.Println()
}()
wg.Add(2) // noop: only support Add(1).
go func() {
defer wg.Done()
}()
var wg1 sync.WaitGroup
wg1.Add(1) // noop: Add and Done should be the same object.
go func() {
defer wg.Done()
fmt.Println()
}()
wg.Add(1) // noop: Add and Done should be the same object.
go func() {
defer wg1.Done()
fmt.Println()
}()
wg.Add(1) // noop: function literal has return values, wg.Go requires func().
go func() int {
defer wg.Done()
return 0
}()
}
// this function puts the unsupported cases of pattern 2.
func _() {
var wg sync.WaitGroup
wg.Add(1)
go func() {
wg.Done()
fmt.Println()
}()
go func() { // noop: no Add call before this go stmt.
fmt.Println()
wg.Done()
}()
var wg1 sync.WaitGroup
wg1.Add(1) // noop: Add and Done should be the same object.
go func() {
fmt.Println()
wg.Done()
}()
wg.Add(1) // noop: Add and Done should be the same object.
go func() {
fmt.Println()
wg1.Done()
}()
wg.Add(1) // noop: function literal has return values, wg.Go requires func().
go func() int {
fmt.Println()
wg.Done()
return 0
}()
}
type Server struct {
wg sync.WaitGroup
}
type ServerContainer struct {
serv Server
}
func _() {
var s Server
s.wg.Add(1)
go func() { // want "Goroutine creation can be simplified using WaitGroup.Go"
print()
s.wg.Done()
}()
var sc ServerContainer
sc.serv.wg.Add(1)
go func() { // want "Goroutine creation can be simplified using WaitGroup.Go"
print()
sc.serv.wg.Done()
}()
var wg sync.WaitGroup
arr := [1]*sync.WaitGroup{&wg}
arr[0].Add(1)
go func() { // want "Goroutine creation can be simplified using WaitGroup.Go"
print()
arr[0].Done()
}()
}
================================================
FILE: go/analysis/passes/modernize/testdata/src/waitgroupgo/waitgroup.go.golden
================================================
package waitgroup
import (
"fmt"
"sync"
)
// supported case for pattern 1.
func _() {
var wg sync.WaitGroup
wg.Go(func() { // want "Goroutine creation can be simplified using WaitGroup.Go"
fmt.Println()
})
wg.Go(func() { // want "Goroutine creation can be simplified using WaitGroup.Go"
})
for range 10 {
wg.Go(func() { // want "Goroutine creation can be simplified using WaitGroup.Go"
fmt.Println()
})
}
}
// supported case for pattern 2.
func _() {
var wg sync.WaitGroup
wg.Go(func() { // want "Goroutine creation can be simplified using WaitGroup.Go"
fmt.Println()
})
wg.Go(func() { // want "Goroutine creation can be simplified using WaitGroup.Go"
})
for range 10 {
wg.Go(func() { // want "Goroutine creation can be simplified using WaitGroup.Go"
fmt.Println()
})
}
}
// this function puts some wrong usages but waitgroupgo modernizer will still offer fixes.
func _() {
var wg sync.WaitGroup
wg.Go(func() { // want "Goroutine creation can be simplified using WaitGroup.Go"
defer wg.Done()
fmt.Println()
})
wg.Go(func() { // want "Goroutine creation can be simplified using WaitGroup.Go"
fmt.Println()
wg.Done()
})
wg.Go(func() { // want "Goroutine creation can be simplified using WaitGroup.Go"
fmt.Println()
wg.Done()
})
}
// this function puts the unsupported cases of pattern 1.
func _() {
var wg sync.WaitGroup
wg.Add(1)
go func() {}()
wg.Add(1)
go func(i int) {
defer wg.Done()
fmt.Println(i)
}(1)
wg.Add(1)
go func() {
fmt.Println()
defer wg.Done()
}()
wg.Add(1)
go func() { // noop: no wg.Done call inside function body.
fmt.Println()
}()
go func() { // noop: no Add call before this go stmt.
defer wg.Done()
fmt.Println()
}()
wg.Add(2) // noop: only support Add(1).
go func() {
defer wg.Done()
}()
var wg1 sync.WaitGroup
wg1.Add(1) // noop: Add and Done should be the same object.
go func() {
defer wg.Done()
fmt.Println()
}()
wg.Add(1) // noop: Add and Done should be the same object.
go func() {
defer wg1.Done()
fmt.Println()
}()
wg.Add(1) // noop: function literal has return values, wg.Go requires func().
go func() int {
defer wg.Done()
return 0
}()
}
// this function puts the unsupported cases of pattern 2.
func _() {
var wg sync.WaitGroup
wg.Add(1)
go func() {
wg.Done()
fmt.Println()
}()
go func() { // noop: no Add call before this go stmt.
fmt.Println()
wg.Done()
}()
var wg1 sync.WaitGroup
wg1.Add(1) // noop: Add and Done should be the same object.
go func() {
fmt.Println()
wg.Done()
}()
wg.Add(1) // noop: Add and Done should be the same object.
go func() {
fmt.Println()
wg1.Done()
}()
wg.Add(1) // noop: function literal has return values, wg.Go requires func().
go func() int {
fmt.Println()
wg.Done()
return 0
}()
}
type Server struct {
wg sync.WaitGroup
}
type ServerContainer struct {
serv Server
}
func _() {
var s Server
s.wg.Go(func() { // want "Goroutine creation can be simplified using WaitGroup.Go"
print()
})
var sc ServerContainer
sc.serv.wg.Go(func() { // want "Goroutine creation can be simplified using WaitGroup.Go"
print()
})
var wg sync.WaitGroup
arr := [1]*sync.WaitGroup{&wg}
arr[0].Go(func() { // want "Goroutine creation can be simplified using WaitGroup.Go"
print()
})
}
================================================
FILE: go/analysis/passes/modernize/testdata/src/waitgroupgo/waitgroup_alias.go
================================================
package waitgroup
import (
"fmt"
sync1 "sync"
)
func _() {
var wg sync1.WaitGroup
wg.Add(1)
go func() { // want "Goroutine creation can be simplified using WaitGroup.Go"
defer wg.Done()
fmt.Println()
}()
wg.Add(1)
go func() { // want "Goroutine creation can be simplified using WaitGroup.Go"
fmt.Println()
wg.Done()
}()
}
================================================
FILE: go/analysis/passes/modernize/testdata/src/waitgroupgo/waitgroup_alias.go.golden
================================================
package waitgroup
import (
"fmt"
sync1 "sync"
)
func _() {
var wg sync1.WaitGroup
wg.Go(func() { // want "Goroutine creation can be simplified using WaitGroup.Go"
fmt.Println()
})
wg.Go(func() { // want "Goroutine creation can be simplified using WaitGroup.Go"
fmt.Println()
})
}
================================================
FILE: go/analysis/passes/modernize/testdata/src/waitgroupgo/waitgroup_dot.go
================================================
package waitgroup
import (
"fmt"
. "sync"
)
// supported case for pattern 1.
func _() {
var wg WaitGroup
wg.Add(1)
go func() { // want "Goroutine creation can be simplified using WaitGroup.Go"
defer wg.Done()
fmt.Println()
}()
wg.Add(1)
go func() { // want "Goroutine creation can be simplified using WaitGroup.Go"
fmt.Println()
wg.Done()
}()
}
================================================
FILE: go/analysis/passes/modernize/testdata/src/waitgroupgo/waitgroup_dot.go.golden
================================================
package waitgroup
import (
"fmt"
. "sync"
)
// supported case for pattern 1.
func _() {
var wg WaitGroup
wg.Go(func() { // want "Goroutine creation can be simplified using WaitGroup.Go"
fmt.Println()
})
wg.Go(func() { // want "Goroutine creation can be simplified using WaitGroup.Go"
fmt.Println()
})
}
================================================
FILE: go/analysis/passes/modernize/testingcontext.go
================================================
// Copyright 2024 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package modernize
import (
"fmt"
"go/ast"
"go/token"
"go/types"
"strings"
"unicode"
"unicode/utf8"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/ast/edge"
"golang.org/x/tools/go/types/typeutil"
"golang.org/x/tools/internal/analysis/analyzerutil"
typeindexanalyzer "golang.org/x/tools/internal/analysis/typeindex"
"golang.org/x/tools/internal/astutil"
"golang.org/x/tools/internal/typesinternal"
"golang.org/x/tools/internal/typesinternal/typeindex"
"golang.org/x/tools/internal/versions"
)
var TestingContextAnalyzer = &analysis.Analyzer{
Name: "testingcontext",
Doc: analyzerutil.MustExtractDoc(doc, "testingcontext"),
Requires: []*analysis.Analyzer{
inspect.Analyzer,
typeindexanalyzer.Analyzer,
},
Run: testingContext,
URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/modernize#testingcontext",
}
// The testingContext pass replaces calls to context.WithCancel from within
// tests to a use of testing.{T,B,F}.Context(), added in Go 1.24.
//
// Specifically, the testingContext pass suggests to replace:
//
// ctx, cancel := context.WithCancel(context.Background()) // or context.TODO
// defer cancel()
//
// with:
//
// ctx := t.Context()
//
// provided:
//
// - ctx and cancel are declared by the assignment
// - the deferred call is the only use of cancel
// - the call is within a test or subtest function
// - the relevant testing.{T,B,F} is named and not shadowed at the call
func testingContext(pass *analysis.Pass) (any, error) {
var (
index = pass.ResultOf[typeindexanalyzer.Analyzer].(*typeindex.Index)
info = pass.TypesInfo
contextWithCancel = index.Object("context", "WithCancel")
)
calls:
for cur := range index.Calls(contextWithCancel) {
call := cur.Node().(*ast.CallExpr)
// Have: context.WithCancel(...)
arg, ok := call.Args[0].(*ast.CallExpr)
if !ok {
continue
}
if !typesinternal.IsFunctionNamed(typeutil.Callee(info, arg), "context", "Background", "TODO") {
continue
}
// Have: context.WithCancel(context.{Background,TODO}())
parent := cur.Parent()
assign, ok := parent.Node().(*ast.AssignStmt)
if !ok || assign.Tok != token.DEFINE {
continue
}
// Have: a, b := context.WithCancel(context.{Background,TODO}())
// Check that both a and b are declared, not redeclarations.
var lhs []types.Object
for _, expr := range assign.Lhs {
id, ok := expr.(*ast.Ident)
if !ok {
continue calls
}
obj, ok := info.Defs[id]
if !ok {
continue calls
}
lhs = append(lhs, obj)
}
next, ok := parent.NextSibling()
if !ok {
continue
}
defr, ok := next.Node().(*ast.DeferStmt)
if !ok {
continue
}
deferId, ok := defr.Call.Fun.(*ast.Ident)
if !ok || !soleUseIs(index, lhs[1], deferId) {
continue // b is used elsewhere
}
// Have:
// a, b := context.WithCancel(context.{Background,TODO}())
// defer b()
// Check that we are in a test func.
var testObj types.Object // relevant testing.{T,B,F}, or nil
if curFunc, ok := enclosingFunc(cur); ok {
switch n := curFunc.Node().(type) {
case *ast.FuncLit:
if ek, idx := curFunc.ParentEdge(); ek == edge.CallExpr_Args && idx == 1 {
// Have: call(..., func(...) { ...context.WithCancel(...)... })
obj := typeutil.Callee(info, curFunc.Parent().Node().(*ast.CallExpr))
if (typesinternal.IsMethodNamed(obj, "testing", "T", "Run") ||
typesinternal.IsMethodNamed(obj, "testing", "B", "Run")) &&
len(n.Type.Params.List[0].Names) == 1 {
// Have tb.Run(..., func(..., tb *testing.[TB]) { ...context.WithCancel(...)... }
testObj = info.Defs[n.Type.Params.List[0].Names[0]]
}
}
case *ast.FuncDecl:
testObj = isTestFn(info, n)
}
}
if testObj != nil && analyzerutil.FileUsesGoVersion(pass, astutil.EnclosingFile(cur), versions.Go1_24) {
// Have a test function. Check that we can resolve the relevant
// testing.{T,B,F} at the current position.
if _, obj := lhs[0].Parent().LookupParent(testObj.Name(), lhs[0].Pos()); obj == testObj {
pass.Report(analysis.Diagnostic{
Pos: call.Fun.Pos(),
End: call.Fun.End(),
Message: fmt.Sprintf("context.WithCancel can be modernized using %s.Context", testObj.Name()),
SuggestedFixes: []analysis.SuggestedFix{{
Message: fmt.Sprintf("Replace context.WithCancel with %s.Context", testObj.Name()),
TextEdits: []analysis.TextEdit{{
Pos: assign.Pos(),
End: defr.End(),
NewText: fmt.Appendf(nil, "%s := %s.Context()", lhs[0].Name(), testObj.Name()),
}},
}},
})
}
}
}
return nil, nil
}
// soleUseIs reports whether id is the sole Ident that uses obj.
// (It returns false if there were no uses of obj.)
func soleUseIs(index *typeindex.Index, obj types.Object, id *ast.Ident) bool {
empty := true
for use := range index.Uses(obj) {
empty = false
if use.Node() != id {
return false
}
}
return !empty
}
// isTestFn checks whether fn is a test function (TestX, BenchmarkX, FuzzX),
// returning the corresponding types.Object of the *testing.{T,B,F} argument.
// Returns nil if fn is a test function, but the testing.{T,B,F} argument is
// unnamed (or _).
//
// TODO(rfindley): consider handling the case of an unnamed argument, by adding
// an edit to give the argument a name.
//
// Adapted from go/analysis/passes/tests.
// TODO(rfindley): consider refactoring to share logic.
func isTestFn(info *types.Info, fn *ast.FuncDecl) types.Object {
// Want functions with 0 results and 1 parameter.
if fn.Type.Results != nil && len(fn.Type.Results.List) > 0 ||
fn.Type.Params == nil ||
len(fn.Type.Params.List) != 1 ||
len(fn.Type.Params.List[0].Names) != 1 {
return nil
}
prefix := testKind(fn.Name.Name)
if prefix == "" {
return nil
}
if tparams := fn.Type.TypeParams; tparams != nil && len(tparams.List) > 0 {
return nil // test functions must not be generic
}
obj := info.Defs[fn.Type.Params.List[0].Names[0]]
if obj == nil {
return nil // e.g. _ *testing.T
}
var name string
switch prefix {
case "Test":
name = "T"
case "Benchmark":
name = "B"
case "Fuzz":
name = "F"
}
if !typesinternal.IsPointerToNamed(obj.Type(), "testing", name) {
return nil
}
return obj
}
// testKind returns "Test", "Benchmark", or "Fuzz" if name is a valid resp.
// test, benchmark, or fuzz function name. Otherwise, isTestName returns "".
//
// Adapted from go/analysis/passes/tests.isTestName.
func testKind(name string) string {
var prefix string
switch {
case strings.HasPrefix(name, "Test"):
prefix = "Test"
case strings.HasPrefix(name, "Benchmark"):
prefix = "Benchmark"
case strings.HasPrefix(name, "Fuzz"):
prefix = "Fuzz"
}
if prefix == "" {
return ""
}
suffix := name[len(prefix):]
if len(suffix) == 0 {
// "Test" is ok.
return prefix
}
r, _ := utf8.DecodeRuneInString(suffix)
if unicode.IsLower(r) {
return ""
}
return prefix
}
================================================
FILE: go/analysis/passes/modernize/unsafefuncs.go
================================================
// Copyright 2025 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package modernize
import (
"fmt"
"go/ast"
"go/token"
"go/types"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/ast/edge"
"golang.org/x/tools/go/ast/inspector"
"golang.org/x/tools/internal/analysis/analyzerutil"
"golang.org/x/tools/internal/astutil"
"golang.org/x/tools/internal/goplsexport"
"golang.org/x/tools/internal/refactor"
"golang.org/x/tools/internal/typesinternal"
"golang.org/x/tools/internal/versions"
)
// TODO(adonovan): also support:
//
// func String(ptr *byte, len IntegerType) string
// func StringData(str string) *byte
// func Slice(ptr *ArbitraryType, len IntegerType) []ArbitraryType
// func SliceData(slice []ArbitraryType) *ArbitraryType
var unsafeFuncsAnalyzer = &analysis.Analyzer{
Name: "unsafefuncs",
Doc: analyzerutil.MustExtractDoc(doc, "unsafefuncs"),
Requires: []*analysis.Analyzer{inspect.Analyzer},
Run: unsafefuncs,
URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/modernize#unsafefuncs",
}
func init() {
// Export to gopls until this is a published modernizer.
goplsexport.UnsafeFuncsModernizer = unsafeFuncsAnalyzer
}
func unsafefuncs(pass *analysis.Pass) (any, error) {
// Short circuit if the package doesn't use unsafe.
// (In theory one could use some imported alias of unsafe.Pointer,
// but let's ignore that.)
if !typesinternal.Imports(pass.Pkg, "unsafe") {
return nil, nil
}
var (
inspect = pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
info = pass.TypesInfo
tUnsafePointer = types.Typ[types.UnsafePointer]
)
// isConversion reports whether e is a conversion T(x).
// If so, it returns T and x.
isConversion := func(curExpr inspector.Cursor) (t types.Type, x inspector.Cursor) {
e := curExpr.Node().(ast.Expr)
if conv, ok := ast.Unparen(e).(*ast.CallExpr); ok && len(conv.Args) == 1 {
if tv := pass.TypesInfo.Types[conv.Fun]; tv.IsType() {
return tv.Type, curExpr.ChildAt(edge.CallExpr_Args, 0)
}
}
return
}
// The general form is where ptr and the result are of type unsafe.Pointer:
//
// unsafe.Pointer(uintptr(ptr) + uintptr(n))
// =>
// unsafe.Add(ptr, n)
// Search for 'unsafe.Pointer(uintptr + uintptr)'
// where the left operand was converted from a pointer.
//
// (Start from sum, not conversion, as it is not
// uncommon to use a local type alias for unsafe.Pointer.)
for curSum := range inspect.Root().Preorder((*ast.BinaryExpr)(nil)) {
if sum, ok := curSum.Node().(*ast.BinaryExpr); ok &&
sum.Op == token.ADD &&
types.Identical(info.TypeOf(sum.X), types.Typ[types.Uintptr]) &&
curSum.ParentEdgeKind() == edge.CallExpr_Args {
// Have: sum ≡ T(x:...uintptr... + y:...uintptr...)
curX := curSum.ChildAt(edge.BinaryExpr_X, -1)
curY := curSum.ChildAt(edge.BinaryExpr_Y, -1)
// Is sum converted to unsafe.Pointer?
curResult := curSum.Parent()
if t, _ := isConversion(curResult); !(t != nil && types.Identical(t, tUnsafePointer)) {
continue
}
// Have: result ≡ unsafe.Pointer(x:...uintptr... + y:...uintptr...)
// Is sum.x converted from unsafe.Pointer?
_, curPtr := isConversion(curX)
if !curPtr.Valid() {
continue
}
ptr := curPtr.Node().(ast.Expr)
if !types.Identical(info.TypeOf(ptr), tUnsafePointer) {
continue
}
// Have: result ≡ unsafe.Pointer(x:uintptr(...unsafe.Pointer...) + y:...uintptr...)
file := astutil.EnclosingFile(curSum)
if !analyzerutil.FileUsesGoVersion(pass, file, versions.Go1_17) {
continue // unsafe.Add not available in this file
}
// import "unsafe"
unsafedot, edits := refactor.AddImport(info, file, "unsafe", "unsafe", "Add", sum.Pos())
// unsafe.Pointer(x + y)
// --------------- -
// x + y
edits = append(edits, deleteConv(curResult)...)
// uintptr (ptr) + offset
// ----------- ---- -
// unsafe.Add(ptr, offset)
edits = append(edits, []analysis.TextEdit{
{
Pos: sum.Pos(),
End: ptr.Pos(),
NewText: fmt.Appendf(nil, "%sAdd(", unsafedot),
},
{
Pos: ptr.End(),
End: sum.Y.Pos(),
NewText: []byte(", "),
},
{
Pos: sum.Y.End(),
End: sum.Y.End(),
NewText: []byte(")"),
},
}...)
// Variant: sum.y operand was converted from another integer type.
// Discard the conversion, as Add is generic over integers.
//
// e.g. unsafe.Pointer(uintptr(ptr) + uintptr(len(s)))
// -------- -
// unsafe.Add ( ptr, len(s))
if t, _ := isConversion(curY); t != nil && isInteger(t) {
edits = append(edits, deleteConv(curY)...)
}
pass.Report(analysis.Diagnostic{
Pos: sum.Pos(),
End: sum.End(),
Message: "pointer + integer can be simplified using unsafe.Add",
SuggestedFixes: []analysis.SuggestedFix{{
Message: "Simplify pointer addition using unsafe.Add",
TextEdits: edits,
}},
})
}
}
return nil, nil
}
// deleteConv returns edits for changing T(x) to x, respecting precedence.
func deleteConv(cur inspector.Cursor) []analysis.TextEdit {
conv := cur.Node().(*ast.CallExpr)
usesPrec := func(n ast.Node) bool {
switch n.(type) {
case *ast.BinaryExpr, *ast.UnaryExpr:
return true
}
return false
}
// Be careful not to change precedence of e.g. T(1+2) * 3.
// TODO(adonovan): refine this.
if usesPrec(cur.Parent().Node()) && usesPrec(conv.Args[0]) {
// T(x+y) * z
// -
// (x+y) * z
return []analysis.TextEdit{{
Pos: conv.Fun.Pos(),
End: conv.Fun.End(),
}}
}
// T(x)
// -- -
// x
return []analysis.TextEdit{
{
Pos: conv.Pos(),
End: conv.Args[0].Pos(),
},
{
Pos: conv.Args[0].End(),
End: conv.End(),
},
}
}
func isInteger(t types.Type) bool {
basic, ok := t.Underlying().(*types.Basic)
return ok && basic.Info()&types.IsInteger != 0
}
================================================
FILE: go/analysis/passes/modernize/waitgroupgo.go
================================================
// Copyright 2025 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package modernize
import (
"bytes"
"fmt"
"go/ast"
"go/printer"
"slices"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/types/typeutil"
"golang.org/x/tools/internal/analysis/analyzerutil"
typeindexanalyzer "golang.org/x/tools/internal/analysis/typeindex"
"golang.org/x/tools/internal/astutil"
"golang.org/x/tools/internal/refactor"
"golang.org/x/tools/internal/typesinternal/typeindex"
"golang.org/x/tools/internal/versions"
)
var WaitGroupGoAnalyzer = &analysis.Analyzer{
Name: "waitgroupgo",
Doc: analyzerutil.MustExtractDoc(doc, "waitgroupgo"),
Requires: []*analysis.Analyzer{
inspect.Analyzer,
typeindexanalyzer.Analyzer,
},
Run: waitgroup,
URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/modernize#waitgroupgo",
}
// The waitgroupgo pass replaces old more complex code with
// go1.25 added API WaitGroup.Go.
//
// Patterns:
//
// 1. wg.Add(1); go func() { defer wg.Done(); ... }()
// =>
// wg.Go(go func() { ... })
//
// 2. wg.Add(1); go func() { ...; wg.Done() }()
// =>
// wg.Go(go func() { ... })
//
// The wg.Done must occur within the first statement of the block in a
// defer format or last statement of the block, and the offered fix
// only removes the first/last wg.Done call. It doesn't fix existing
// wrong usage of sync.WaitGroup.
//
// The use of WaitGroup.Go in pattern 1 implicitly introduces a
// 'defer', which may change the behavior in the case of panic from
// the "..." logic. In this instance, the change is safe: before and
// after the transformation, an unhandled panic inevitably results in
// a fatal crash. The fact that the transformed code calls wg.Done()
// before the crash doesn't materially change anything. (If Done had
// other effects, or blocked, or if WaitGroup.Go propagated panics
// from child to parent goroutine, the argument would be different.)
func waitgroup(pass *analysis.Pass) (any, error) {
var (
index = pass.ResultOf[typeindexanalyzer.Analyzer].(*typeindex.Index)
info = pass.TypesInfo
syncWaitGroupAdd = index.Selection("sync", "WaitGroup", "Add")
syncWaitGroupDone = index.Selection("sync", "WaitGroup", "Done")
)
if !index.Used(syncWaitGroupDone) {
return nil, nil
}
for curAddCall := range index.Calls(syncWaitGroupAdd) {
// Extract receiver from wg.Add call.
addCall := curAddCall.Node().(*ast.CallExpr)
if !isIntLiteral(info, addCall.Args[0], 1) {
continue // not a call to wg.Add(1)
}
// Inv: the Args[0] check ensures addCall is not of
// the form sync.WaitGroup.Add(&wg, 1).
addCallRecv := ast.Unparen(addCall.Fun).(*ast.SelectorExpr).X
// Following statement must be go func() { ... } ().
curAddStmt := curAddCall.Parent()
if !is[*ast.ExprStmt](curAddStmt.Node()) {
continue // unnecessary parens?
}
curNext, ok := curAddCall.Parent().NextSibling()
if !ok {
continue // no successor
}
goStmt, ok := curNext.Node().(*ast.GoStmt)
if !ok {
continue // not a go stmt
}
lit, ok := goStmt.Call.Fun.(*ast.FuncLit)
if !ok || len(goStmt.Call.Args) != 0 {
continue // go argument is not func(){...}()
}
if lit.Type.Results != nil && len(lit.Type.Results.List) > 0 {
continue // function literal has return values; wg.Go requires func()
}
list := lit.Body.List
if len(list) == 0 {
continue
}
// Body must start with "defer wg.Done()" or end with "wg.Done()".
var doneStmt ast.Stmt
if deferStmt, ok := list[0].(*ast.DeferStmt); ok &&
typeutil.Callee(info, deferStmt.Call) == syncWaitGroupDone &&
astutil.EqualSyntax(ast.Unparen(deferStmt.Call.Fun).(*ast.SelectorExpr).X, addCallRecv) {
doneStmt = deferStmt // "defer wg.Done()"
} else if lastStmt, ok := list[len(list)-1].(*ast.ExprStmt); ok {
if doneCall, ok := lastStmt.X.(*ast.CallExpr); ok &&
typeutil.Callee(info, doneCall) == syncWaitGroupDone &&
astutil.EqualSyntax(ast.Unparen(doneCall.Fun).(*ast.SelectorExpr).X, addCallRecv) {
doneStmt = lastStmt // "wg.Done()"
}
}
if doneStmt == nil {
continue
}
curDoneStmt, ok := curNext.FindNode(doneStmt)
if !ok {
panic("can't find Cursor for 'done' statement")
}
file := astutil.EnclosingFile(curAddCall)
if !analyzerutil.FileUsesGoVersion(pass, file, versions.Go1_25) {
continue
}
tokFile := pass.Fset.File(file.Pos())
var addCallRecvText bytes.Buffer
err := printer.Fprint(&addCallRecvText, pass.Fset, addCallRecv)
if err != nil {
continue // error getting text for the edit
}
pass.Report(analysis.Diagnostic{
// go func() {
// ~~~~~~~~~
Pos: goStmt.Pos(),
End: lit.Type.End(),
Message: "Goroutine creation can be simplified using WaitGroup.Go",
SuggestedFixes: []analysis.SuggestedFix{{
Message: "Simplify by using WaitGroup.Go",
TextEdits: slices.Concat(
// delete "wg.Add(1)"
refactor.DeleteStmt(tokFile, curAddStmt),
// delete "wg.Done()" or "defer wg.Done()"
refactor.DeleteStmt(tokFile, curDoneStmt),
[]analysis.TextEdit{
// go func()
// ------
// wg.Go(func()
{
Pos: goStmt.Pos(),
End: goStmt.Call.Pos(),
NewText: fmt.Appendf(nil, "%s.Go(", addCallRecvText.String()),
},
// ... }()
// -
// ... } )
{
Pos: goStmt.Call.Lparen,
End: goStmt.Call.Rparen,
},
},
),
}},
})
}
return nil, nil
}
================================================
FILE: go/analysis/passes/nilfunc/doc.go
================================================
// Copyright 2023 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package nilfunc defines an Analyzer that checks for useless
// comparisons against nil.
//
// # Analyzer nilfunc
//
// nilfunc: check for useless comparisons between functions and nil
//
// A useless comparison is one like f == nil as opposed to f() == nil.
package nilfunc
================================================
FILE: go/analysis/passes/nilfunc/nilfunc.go
================================================
// Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package nilfunc defines an Analyzer that checks for useless
// comparisons against nil.
package nilfunc
import (
_ "embed"
"go/ast"
"go/token"
"go/types"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/ast/inspector"
"golang.org/x/tools/internal/analysis/analyzerutil"
"golang.org/x/tools/internal/typesinternal"
)
//go:embed doc.go
var doc string
var Analyzer = &analysis.Analyzer{
Name: "nilfunc",
Doc: analyzerutil.MustExtractDoc(doc, "nilfunc"),
URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/nilfunc",
Requires: []*analysis.Analyzer{inspect.Analyzer},
Run: run,
}
func run(pass *analysis.Pass) (any, error) {
inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
nodeFilter := []ast.Node{
(*ast.BinaryExpr)(nil),
}
inspect.Preorder(nodeFilter, func(n ast.Node) {
e := n.(*ast.BinaryExpr)
// Only want == or != comparisons.
if e.Op != token.EQL && e.Op != token.NEQ {
return
}
// Only want comparisons with a nil identifier on one side.
var e2 ast.Expr
switch {
case pass.TypesInfo.Types[e.X].IsNil():
e2 = e.Y
case pass.TypesInfo.Types[e.Y].IsNil():
e2 = e.X
default:
return
}
// Only want functions.
obj := pass.TypesInfo.Uses[typesinternal.UsedIdent(pass.TypesInfo, e2)]
if _, ok := obj.(*types.Func); !ok {
return
}
pass.ReportRangef(e, "comparison of function %v %v nil is always %v", obj.Name(), e.Op, e.Op == token.NEQ)
})
return nil, nil
}
================================================
FILE: go/analysis/passes/nilfunc/nilfunc_test.go
================================================
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package nilfunc_test
import (
"testing"
"golang.org/x/tools/go/analysis/analysistest"
"golang.org/x/tools/go/analysis/passes/nilfunc"
)
func Test(t *testing.T) {
testdata := analysistest.TestData()
analysistest.Run(t, testdata, nilfunc.Analyzer, "a", "typeparams")
}
================================================
FILE: go/analysis/passes/nilfunc/testdata/src/a/a.go
================================================
// Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package a
func F() {}
type T struct {
F func()
}
func (T) M() {}
var Fv = F
func Comparison() {
var t T
var fn func()
if fn == nil || Fv == nil || t.F == nil {
// no error; these func vars or fields may be nil
}
if F == nil { // want "comparison of function F == nil is always false"
panic("can't happen")
}
if t.M == nil { // want "comparison of function M == nil is always false"
panic("can't happen")
}
if F != nil { // want "comparison of function F != nil is always true"
if t.M != nil { // want "comparison of function M != nil is always true"
return
}
}
panic("can't happen")
}
================================================
FILE: go/analysis/passes/nilfunc/testdata/src/typeparams/typeparams.go
================================================
// Copyright 2021 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// This file contains tests for the lostcancel checker.
package typeparams
func f[P any]() {}
func g[P1 any, P2 any](x P1) {}
var f1 = f[int]
type T1[P any] struct {
f func() P
}
type T2[P1 any, P2 any] struct {
g func(P1) P2
}
func Comparison[P any](f2 func() T1[P]) {
var t1 T1[P]
var t2 T2[P, int]
var fn func()
if fn == nil || f1 == nil || f2 == nil || t1.f == nil || t2.g == nil {
// no error; these func vars or fields may be nil
}
if f[P] == nil { // want "comparison of function f == nil is always false"
panic("can't happen")
}
if f[int] == nil { // want "comparison of function f == nil is always false"
panic("can't happen")
}
if g[P, int] == nil { // want "comparison of function g == nil is always false"
panic("can't happen")
}
}
func Index[P any](a [](func() P)) {
if a[1] == nil {
// no error
}
var t1 []T1[P]
var t2 [][]T2[P, P]
if t1[1].f == nil || t2[0][1].g == nil {
// no error
}
}
================================================
FILE: go/analysis/passes/nilness/cmd/nilness/main.go
================================================
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// The nilness command applies the golang.org/x/tools/go/analysis/passes/nilness
// analysis to the specified packages of Go source code.
package main
import (
"golang.org/x/tools/go/analysis/passes/nilness"
"golang.org/x/tools/go/analysis/singlechecker"
)
func main() { singlechecker.Main(nilness.Analyzer) }
================================================
FILE: go/analysis/passes/nilness/doc.go
================================================
// Copyright 2023 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package nilness inspects the control-flow graph of an SSA function
// and reports errors such as nil pointer dereferences and degenerate
// nil pointer comparisons.
//
// # Analyzer nilness
//
// nilness: check for redundant or impossible nil comparisons
//
// The nilness checker inspects the control-flow graph of each function in
// a package and reports nil pointer dereferences, degenerate nil
// pointers, and panics with nil values. A degenerate comparison is of the form
// x==nil or x!=nil where x is statically known to be nil or non-nil. These are
// often a mistake, especially in control flow related to errors. Panics with nil
// values are checked because they are not detectable by
//
// if r := recover(); r != nil {
//
// This check reports conditions such as:
//
// if f == nil { // impossible condition (f is a function)
// }
//
// and:
//
// p := &v
// ...
// if p != nil { // tautological condition
// }
//
// and:
//
// if p == nil {
// print(*p) // nil dereference
// }
//
// and:
//
// if p == nil {
// panic(p)
// }
//
// Sometimes the control flow may be quite complex, making bugs hard
// to spot. In the example below, the err.Error expression is
// guaranteed to panic because, after the first return, err must be
// nil. The intervening loop is just a distraction.
//
// ...
// err := g.Wait()
// if err != nil {
// return err
// }
// partialSuccess := false
// for _, err := range errs {
// if err == nil {
// partialSuccess = true
// break
// }
// }
// if partialSuccess {
// reportStatus(StatusMessage{
// Code: code.ERROR,
// Detail: err.Error(), // "nil dereference in dynamic method call"
// })
// return nil
// }
//
// ...
package nilness
================================================
FILE: go/analysis/passes/nilness/nilness.go
================================================
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package nilness
import (
_ "embed"
"fmt"
"go/token"
"go/types"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/buildssa"
"golang.org/x/tools/go/ssa"
"golang.org/x/tools/internal/analysis/analyzerutil"
"golang.org/x/tools/internal/typeparams"
)
//go:embed doc.go
var doc string
var Analyzer = &analysis.Analyzer{
Name: "nilness",
Doc: analyzerutil.MustExtractDoc(doc, "nilness"),
URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/nilness",
Run: run,
Requires: []*analysis.Analyzer{buildssa.Analyzer},
}
func run(pass *analysis.Pass) (any, error) {
ssainput := pass.ResultOf[buildssa.Analyzer].(*buildssa.SSA)
for _, fn := range ssainput.SrcFuncs {
runFunc(pass, fn)
}
return nil, nil
}
func runFunc(pass *analysis.Pass, fn *ssa.Function) {
reportf := func(category string, pos token.Pos, format string, args ...any) {
// We ignore nil-checking ssa.Instructions
// that don't correspond to syntax.
if pos.IsValid() {
pass.Report(analysis.Diagnostic{
Pos: pos,
Category: category,
Message: fmt.Sprintf(format, args...),
})
}
}
// notNil reports an error if v is provably nil.
notNil := func(stack []fact, instr ssa.Instruction, v ssa.Value, descr string) {
if nilnessOf(stack, v) == isnil {
reportf("nilderef", instr.Pos(), "%s", descr)
}
}
// visit visits reachable blocks of the CFG in dominance order,
// maintaining a stack of dominating nilness facts.
//
// By traversing the dom tree, we can pop facts off the stack as
// soon as we've visited a subtree. Had we traversed the CFG,
// we would need to retain the set of facts for each block.
seen := make([]bool, len(fn.Blocks)) // seen[i] means visit should ignore block i
var visit func(b *ssa.BasicBlock, stack []fact)
visit = func(b *ssa.BasicBlock, stack []fact) {
if seen[b.Index] {
return
}
seen[b.Index] = true
// Report nil dereferences.
for _, instr := range b.Instrs {
switch instr := instr.(type) {
case ssa.CallInstruction:
// A nil receiver may be okay for type params.
cc := instr.Common()
if !(cc.IsInvoke() && typeparams.IsTypeParam(cc.Value.Type())) {
notNil(stack, instr, cc.Value, "nil dereference in "+cc.Description())
}
case *ssa.FieldAddr:
notNil(stack, instr, instr.X, "nil dereference in field selection")
case *ssa.IndexAddr:
switch typeparams.CoreType(instr.X.Type()).(type) {
case *types.Pointer: // *array
notNil(stack, instr, instr.X, "nil dereference in array index operation")
case *types.Slice:
// This is not necessarily a runtime error, because
// it is usually dominated by a bounds check.
if isRangeIndex(instr) {
notNil(stack, instr, instr.X, "range of nil slice")
} else {
notNil(stack, instr, instr.X, "index of nil slice")
}
}
case *ssa.MapUpdate:
notNil(stack, instr, instr.Map, "nil dereference in map update")
case *ssa.Range:
// (Not a runtime error, but a likely mistake.)
notNil(stack, instr, instr.X, "range over nil map")
case *ssa.Slice:
// A nilcheck occurs in ptr[:] iff ptr is a pointer to an array.
if is[*types.Pointer](instr.X.Type().Underlying()) {
notNil(stack, instr, instr.X, "nil dereference in slice operation")
}
case *ssa.Store:
notNil(stack, instr, instr.Addr, "nil dereference in store")
case *ssa.TypeAssert:
if !instr.CommaOk {
notNil(stack, instr, instr.X, "nil dereference in type assertion")
}
case *ssa.UnOp:
switch instr.Op {
case token.MUL: // *X
notNil(stack, instr, instr.X, "nil dereference in load")
case token.ARROW: // <-ch
// (Not a runtime error, but a likely mistake.)
notNil(stack, instr, instr.X, "receive from nil channel")
}
case *ssa.Send:
// (Not a runtime error, but a likely mistake.)
notNil(stack, instr, instr.Chan, "send to nil channel")
}
}
// Look for panics with nil value
for _, instr := range b.Instrs {
switch instr := instr.(type) {
case *ssa.Panic:
if nilnessOf(stack, instr.X) == isnil {
reportf("nilpanic", instr.Pos(), "panic with nil value")
}
case *ssa.SliceToArrayPointer:
nn := nilnessOf(stack, instr.X)
if nn == isnil && slice2ArrayPtrLen(instr) > 0 {
reportf("conversionpanic", instr.Pos(), "nil slice being cast to an array of len > 0 will always panic")
}
}
}
// For nil comparison blocks, report an error if the condition
// is degenerate, and push a nilness fact on the stack when
// visiting its true and false successor blocks.
if binop, tsucc, fsucc := eq(b); binop != nil {
xnil := nilnessOf(stack, binop.X)
ynil := nilnessOf(stack, binop.Y)
if ynil != unknown && xnil != unknown && (xnil == isnil || ynil == isnil) {
// Degenerate condition:
// the nilness of both operands is known,
// and at least one of them is nil.
var adj string
if (xnil == ynil) == (binop.Op == token.EQL) {
adj = "tautological"
} else {
adj = "impossible"
}
reportf("cond", binop.Pos(), "%s condition: %s %s %s", adj, xnil, binop.Op, ynil)
// If tsucc's or fsucc's sole incoming edge is impossible,
// it is unreachable. Prune traversal of it and
// all the blocks it dominates.
// (We could be more precise with full dataflow
// analysis of control-flow joins.)
var skip *ssa.BasicBlock
if xnil == ynil {
skip = fsucc
} else {
skip = tsucc
}
for _, d := range b.Dominees() {
if d == skip && len(d.Preds) == 1 {
continue
}
visit(d, stack)
}
return
}
// "if x == nil" or "if nil == y" condition; x, y are unknown.
if xnil == isnil || ynil == isnil {
var newFacts facts
if xnil == isnil {
// x is nil, y is unknown:
// t successor learns y is nil.
newFacts = expandFacts(fact{binop.Y, isnil})
} else {
// y is nil, x is unknown:
// t successor learns x is nil.
newFacts = expandFacts(fact{binop.X, isnil})
}
for _, d := range b.Dominees() {
// Successor blocks learn a fact
// only at non-critical edges.
// (We could do be more precise with full dataflow
// analysis of control-flow joins.)
s := stack
if len(d.Preds) == 1 {
if d == tsucc {
s = append(s, newFacts...)
} else if d == fsucc {
s = append(s, newFacts.negate()...)
}
}
visit(d, s)
}
return
}
}
// In code of the form:
//
// if ptr, ok := x.(*T); ok { ... } else { fsucc }
//
// the fsucc block learns that ptr == nil,
// since that's its zero value.
if If, ok := b.Instrs[len(b.Instrs)-1].(*ssa.If); ok {
// Handle "if ok" and "if !ok" variants.
cond, fsucc := If.Cond, b.Succs[1]
if unop, ok := cond.(*ssa.UnOp); ok && unop.Op == token.NOT {
cond, fsucc = unop.X, b.Succs[0]
}
// Match pattern:
// t0 = typeassert (pointerlike)
// t1 = extract t0 #0 // ptr
// t2 = extract t0 #1 // ok
// if t2 goto tsucc, fsucc
if extract1, ok := cond.(*ssa.Extract); ok && extract1.Index == 1 {
if assert, ok := extract1.Tuple.(*ssa.TypeAssert); ok &&
isNillable(assert.AssertedType) {
for _, pinstr := range *assert.Referrers() {
if extract0, ok := pinstr.(*ssa.Extract); ok &&
extract0.Index == 0 &&
extract0.Tuple == extract1.Tuple {
for _, d := range b.Dominees() {
if len(d.Preds) == 1 && d == fsucc {
visit(d, append(stack, fact{extract0, isnil}))
}
}
}
}
}
}
}
for _, d := range b.Dominees() {
visit(d, stack)
}
}
// Visit the entry block. No need to visit fn.Recover.
if fn.Blocks != nil {
visit(fn.Blocks[0], make([]fact, 0, 20)) // 20 is plenty
}
}
// A fact records that a block is dominated
// by the condition v == nil or v != nil.
type fact struct {
value ssa.Value
nilness nilness
}
func (f fact) negate() fact { return fact{f.value, -f.nilness} }
type nilness int
const (
isnonnil = -1
unknown nilness = 0
isnil = 1
)
var nilnessStrings = []string{"non-nil", "unknown", "nil"}
func (n nilness) String() string { return nilnessStrings[n+1] }
// nilnessOf reports whether v is definitely nil, definitely not nil,
// or unknown given the dominating stack of facts.
func nilnessOf(stack []fact, v ssa.Value) nilness {
switch v := v.(type) {
// unwrap ChangeInterface and Slice values recursively, to detect if underlying
// values have any facts recorded or are otherwise known with regard to nilness.
//
// This work must be in addition to expanding facts about
// ChangeInterfaces during inference/fact gathering because this covers
// cases where the nilness of a value is intrinsic, rather than based
// on inferred facts, such as a zero value interface variable. That
// said, this work alone would only inform us when facts are about
// underlying values, rather than outer values, when the analysis is
// transitive in both directions.
case *ssa.ChangeInterface:
if underlying := nilnessOf(stack, v.X); underlying != unknown {
return underlying
}
case *ssa.MakeInterface:
// A MakeInterface is non-nil unless its operand is a type parameter.
tparam, ok := types.Unalias(v.X.Type()).(*types.TypeParam)
if !ok {
return isnonnil
}
// A MakeInterface of a type parameter is non-nil if
// the type parameter cannot be instantiated as an
// interface type (#66835).
if terms, err := typeparams.NormalTerms(tparam.Constraint()); err == nil && len(terms) > 0 {
return isnonnil
}
// If the type parameter can be instantiated as an
// interface (and thus also as a concrete type),
// we can't determine the nilness.
case *ssa.Slice:
if underlying := nilnessOf(stack, v.X); underlying != unknown {
return underlying
}
case *ssa.SliceToArrayPointer:
nn := nilnessOf(stack, v.X)
if slice2ArrayPtrLen(v) > 0 {
if nn == isnil {
// We know that *(*[1]byte)(nil) is going to panic because of the
// conversion. So return unknown to the caller, prevent useless
// nil deference reporting due to * operator.
return unknown
}
// Otherwise, the conversion will yield a non-nil pointer to array.
// Note that the instruction can still panic if array length greater
// than slice length. If the value is used by another instruction,
// that instruction can assume the panic did not happen when that
// instruction is reached.
return isnonnil
}
// In case array length is zero, the conversion result depends on nilness of the slice.
if nn != unknown {
return nn
}
}
// Is value intrinsically nil or non-nil?
switch v := v.(type) {
case *ssa.Alloc,
*ssa.FieldAddr,
*ssa.FreeVar,
*ssa.Function,
*ssa.Global,
*ssa.IndexAddr,
*ssa.MakeChan,
*ssa.MakeClosure,
*ssa.MakeMap,
*ssa.MakeSlice:
return isnonnil
case *ssa.Const:
if v.IsNil() {
return isnil // nil or zero value of a pointer-like type
} else {
return unknown // non-pointer
}
}
// Search dominating control-flow facts.
for _, f := range stack {
if f.value == v {
return f.nilness
}
}
return unknown
}
func slice2ArrayPtrLen(v *ssa.SliceToArrayPointer) int64 {
return v.Type().(*types.Pointer).Elem().Underlying().(*types.Array).Len()
}
// If b ends with an equality comparison, eq returns the operation and
// its true (equal) and false (not equal) successors.
func eq(b *ssa.BasicBlock) (op *ssa.BinOp, tsucc, fsucc *ssa.BasicBlock) {
if If, ok := b.Instrs[len(b.Instrs)-1].(*ssa.If); ok {
if binop, ok := If.Cond.(*ssa.BinOp); ok {
switch binop.Op {
case token.EQL:
return binop, b.Succs[0], b.Succs[1]
case token.NEQ:
return binop, b.Succs[1], b.Succs[0]
}
}
}
return nil, nil, nil
}
// expandFacts takes a single fact and returns the set of facts that can be
// known about it or any of its related values. Some operations, like
// ChangeInterface, have transitive nilness, such that if you know the
// underlying value is nil, you also know the value itself is nil, and vice
// versa. This operation allows callers to match on any of the related values
// in analyses, rather than just the one form of the value that happened to
// appear in a comparison.
//
// This work must be in addition to unwrapping values within nilnessOf because
// while this work helps give facts about transitively known values based on
// inferred facts, the recursive check within nilnessOf covers cases where
// nilness facts are intrinsic to the underlying value, such as a zero value
// interface variables.
//
// ChangeInterface is the only expansion currently supported, but others, like
// Slice, could be added. At this time, this tool does not check slice
// operations in a way this expansion could help. See
// https://play.golang.org/p/mGqXEp7w4fR for an example.
func expandFacts(f fact) []fact {
ff := []fact{f}
Loop:
for {
switch v := f.value.(type) {
case *ssa.ChangeInterface:
f = fact{v.X, f.nilness}
ff = append(ff, f)
default:
break Loop
}
}
return ff
}
type facts []fact
func (ff facts) negate() facts {
nn := make([]fact, len(ff))
for i, f := range ff {
nn[i] = f.negate()
}
return nn
}
func is[T any](x any) bool {
_, ok := x.(T)
return ok
}
func isNillable(t types.Type) bool {
// TODO(adonovan): CoreType (+ case *Interface) looks wrong.
// This should probably use Underlying, and handle TypeParam
// by computing the union across its normal terms.
switch t := typeparams.CoreType(t).(type) {
case *types.Pointer,
*types.Map,
*types.Signature,
*types.Chan,
*types.Interface,
*types.Slice:
return true
case *types.Basic:
return t == types.Typ[types.UnsafePointer]
}
return false
}
// isRangeIndex reports whether the instruction is a slice indexing
// operation slice[i] within a "for range slice" loop. The operation
// could be explicit, such as slice[i] within (or even after) the
// loop, or it could be implicit, such as "for i, v := range slice {}".
// (These cannot be reliably distinguished.)
func isRangeIndex(instr *ssa.IndexAddr) bool {
// Here we reverse-engineer the go/ssa lowering of range-over-slice:
//
// n = len(x)
// jump loop
// loop: "rangeindex.loop"
// phi = φ(-1, incr) #rangeindex
// incr = phi + 1
// cond = incr < n
// if cond goto body else done
// body: "rangeindex.body"
// instr = &x[incr]
// ...
// done:
if incr, ok := instr.Index.(*ssa.BinOp); ok && incr.Op == token.ADD {
if b := incr.Block(); b.Comment == "rangeindex.loop" {
if If, ok := b.Instrs[len(b.Instrs)-1].(*ssa.If); ok {
if cond := If.Cond.(*ssa.BinOp); cond.X == incr && cond.Op == token.LSS {
if call, ok := cond.Y.(*ssa.Call); ok {
common := call.Common()
if blt, ok := common.Value.(*ssa.Builtin); ok && blt.Name() == "len" {
return common.Args[0] == instr.X
}
}
}
}
}
}
return false
}
================================================
FILE: go/analysis/passes/nilness/nilness_test.go
================================================
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package nilness_test
import (
"testing"
"golang.org/x/tools/go/analysis/analysistest"
"golang.org/x/tools/go/analysis/passes/nilness"
)
func Test(t *testing.T) {
testdata := analysistest.TestData()
analysistest.Run(t, testdata, nilness.Analyzer, "a")
}
func TestNilness(t *testing.T) {
testdata := analysistest.TestData()
analysistest.Run(t, testdata, nilness.Analyzer, "b")
}
func TestInstantiated(t *testing.T) {
testdata := analysistest.TestData()
analysistest.Run(t, testdata, nilness.Analyzer, "c")
}
func TestTypeSet(t *testing.T) {
testdata := analysistest.TestData()
analysistest.Run(t, testdata, nilness.Analyzer, "d")
}
================================================
FILE: go/analysis/passes/nilness/testdata/src/a/a.go
================================================
package a
import (
"log"
"os"
)
type X struct{ f, g int }
func f(x, y *X) {
if x == nil {
print(x.f) // want "nil dereference in field selection"
} else {
print(x.f)
}
if x == nil {
if nil != y {
print(1)
panic(0)
}
x.f = 1 // want "nil dereference in field selection"
y.f = 1 // want "nil dereference in field selection"
}
var f func()
if f == nil { // want "tautological condition: nil == nil"
go f() // want "nil dereference in dynamic function call"
} else {
// This block is unreachable,
// so we don't report an error for the
// nil dereference in the call.
defer f()
}
}
func f2(ptr *[3]int, i interface{}) {
if ptr != nil {
print(ptr[:])
*ptr = [3]int{}
print(*ptr)
} else {
print(ptr[:]) // want "nil dereference in slice operation"
*ptr = [3]int{} // want "nil dereference in store"
print(*ptr) // want "nil dereference in load"
if ptr != nil { // want "impossible condition: nil != nil"
// Dominated by ptr==nil and ptr!=nil,
// this block is unreachable.
// We do not report errors within it.
print(*ptr)
}
}
if i != nil {
print(i.(interface{ f() }))
} else {
print(i.(interface{ f() })) // want "nil dereference in type assertion"
}
}
func g() error { return nil }
func f3() error {
err := g()
if err != nil {
return err
}
if err != nil && err.Error() == "foo" { // want "impossible condition: nil != nil"
print(0)
}
ch := make(chan int)
if ch == nil { // want "impossible condition: non-nil == nil"
print(0)
}
if ch != nil { // want "tautological condition: non-nil != nil"
print(0)
}
return nil
}
func h(err error, b bool) {
if err != nil && b {
return
} else if err != nil {
panic(err)
}
}
func i(*int) error {
for {
if err := g(); err != nil {
return err
}
}
}
func f4(x *X) {
if x == nil {
panic(x)
}
}
func f5(x *X) {
panic(nil) // want "panic with nil value"
}
func f6(x *X) {
var err error
panic(err) // want "panic with nil value"
}
func f7() {
x, err := bad()
if err != nil {
panic(0)
}
if x == nil {
panic(err) // want "panic with nil value"
}
}
func bad() (*X, error) {
return nil, nil
}
func f8() {
var e error
v, _ := e.(interface{})
print(v)
}
func f9(x interface {
a()
b()
c()
}) {
x.b() // we don't catch this panic because we don't have any facts yet
xx := interface {
a()
b()
}(x)
if xx != nil {
return
}
x.c() // want "nil dereference in dynamic method call"
xx.b() // want "nil dereference in dynamic method call"
xxx := interface{ a() }(xx)
xxx.a() // want "nil dereference in dynamic method call"
if unknown() {
panic(x) // want "panic with nil value"
}
if unknown() {
panic(xx) // want "panic with nil value"
}
if unknown() {
panic(xxx) // want "panic with nil value"
}
}
func f10() {
s0 := make([]string, 0)
if s0 == nil { // want "impossible condition: non-nil == nil"
print(0)
}
var s1 []string
if s1 == nil { // want "tautological condition: nil == nil"
print(0)
}
s2 := s1[:][:]
if s2 == nil { // want "tautological condition: nil == nil"
print(0)
}
}
func unknown() bool {
return false
}
func f11(a interface{}) {
switch a.(type) {
case nil:
return
}
switch a.(type) {
case nil: // want "impossible condition: non-nil == nil"
return
}
}
func f12(a interface{}) {
switch a {
case nil:
return
}
switch a {
case 5,
nil: // want "impossible condition: non-nil == nil"
return
}
}
type Y struct {
innerY
}
type innerY struct {
value int
}
func f13() {
var d *Y
print(d.value) // want "nil dereference in field selection"
}
func f14() {
var x struct{ f string }
if x == struct{ f string }{} { // we don't catch this tautology as we restrict to reference types
print(x)
}
}
func f15(x any) {
ptr, ok := x.(*int)
if ok {
return
}
println(*ptr) // want "nil dereference in load"
}
func f16(x any) {
ptr, ok := x.(*int)
if !ok {
println(*ptr) // want "nil dereference in load"
return
}
println(*ptr)
}
func f18(x any) {
ptr, ok := x.(*int)
if ok {
println(ptr)
// falls through
}
println(*ptr)
}
// Regression test for https://github.com/golang/go/issues/65674:
// spurious "nil deference in slice index operation" when the
// index was subject to a range loop.
func f19(slice []int, array *[2]int, m map[string]int, ch chan int) {
if slice == nil {
// A range over a nil slice is dynamically benign,
// but still signifies a programmer mistake.
//
// Since SSA has melted down the control structure,
// so we can only report a diagnostic about the
// index operation, with heuristics for "range".
for range slice { // nothing to report here
}
for _, v := range slice { // want "range of nil slice"
_ = v
}
for i := range slice {
_ = slice[i] // want "range of nil slice"
}
{
var i int
for i = range slice {
}
_ = slice[i] // want "index of nil slice"
}
for i := range slice {
if i < len(slice) {
_ = slice[i] // want "range of nil slice"
}
}
if len(slice) > 3 {
_ = slice[2] // want "index of nil slice"
}
for i := 0; i < len(slice); i++ {
_ = slice[i] // want "index of nil slice"
}
}
if array == nil {
// (The v var is necessary, otherwise the SSA
// code doesn't dereference the pointer.)
for _, v := range array { // want "nil dereference in array index operation"
_ = v
}
}
if m == nil {
for range m { // want "range over nil map"
}
m["one"] = 1 // want "nil dereference in map update"
}
if ch == nil {
for range ch { // want "receive from nil channel"
}
<-ch // want "receive from nil channel"
ch <- 0 // want "send to nil channel"
}
}
func f20() {
f, err := os.Open("")
if err != nil {
log.Fatal(err) // noreturn analysis proves this call doesn't return
}
if err != nil { // want "impossible condition"
log.Fatal(err)
}
f.Close()
}
================================================
FILE: go/analysis/passes/nilness/testdata/src/b/b.go
================================================
package b
func f() {
var s []int
t := (*[0]int)(s)
_ = *t // want "nil dereference in load"
_ = (*[0]int)(s)
_ = *(*[0]int)(s) // want "nil dereference in load"
// these operation is panic
_ = (*[1]int)(s) // want "nil slice being cast to an array of len > 0 will always panic"
_ = *(*[1]int)(s) // want "nil slice being cast to an array of len > 0 will always panic"
}
func g() {
var s = make([]int, 0)
t := (*[0]int)(s)
println(*t)
}
func h() {
var s = make([]int, 1)
t := (*[1]int)(s)
println(*t)
}
func i(x []int) {
a := (*[1]int)(x)
if a != nil { // want "tautological condition: non-nil != nil"
_ = *a
}
}
func _(err error) {
if err == nil {
err.Error() // want "nil dereference in dynamic method call"
// SSA uses TypeAssert for the nil check in a method value:
_ = err.Error // want "nil dereference in type assertion"
}
}
================================================
FILE: go/analysis/passes/nilness/testdata/src/c/c.go
================================================
package c
func instantiated[X any](x *X) int {
if x == nil {
print(*x) // want "nil dereference in load"
}
return 1
}
var g int
func init() {
g = instantiated[int](&g)
}
// -- issue 66835 --
type Empty1 any
type Empty2 any
// T may be instantiated with an interface type, so any(x) may be nil.
func TypeParamInterface[T error](x T) {
if any(x) == nil {
print()
}
}
// T may not be instantiated with an interface type, so any(x) is non-nil
func TypeParamTypeSetWithInt[T interface {
error
int
}](x T) {
if any(x) == nil { // want "impossible condition: non-nil == nil"
print()
}
}
func TypeParamUnionEmptyEmpty[T Empty1 | Empty2](x T) {
if any(x) == nil {
print()
}
}
func TypeParamUnionEmptyInt[T Empty1 | int](x T) {
if any(x) == nil {
print()
}
}
func TypeParamUnionStringInt[T string | int](x T) {
if any(x) == nil { // want "impossible condition: non-nil == nil"
print()
}
}
================================================
FILE: go/analysis/passes/nilness/testdata/src/d/d.go
================================================
package d
type message interface{ PR() }
func noparam() {
var messageT message
messageT.PR() // want "nil dereference in dynamic method call"
}
func paramNonnil[T message]() {
var messageT T
messageT.PR() // cannot conclude messageT is nil.
}
func instance() {
// buildssa.BuilderMode does not include InstantiateGenerics.
paramNonnil[message]() // no warning is expected as param[message] id not built.
}
func param[T interface {
message
~*int | ~chan int
}]() {
var messageT T // messageT is nil.
messageT.PR() // nil receiver may be okay. See param[nilMsg].
}
type nilMsg chan int
func (m nilMsg) PR() {
if m == nil {
print("not an error")
}
}
var G func() = param[nilMsg] // no warning
func allNillable[T ~*int | ~chan int]() {
var x, y T // both are nillable and are nil.
if x != y { // want "impossible condition: nil != nil"
print("unreachable")
}
}
func notAll[T ~*int | ~chan int | ~int]() {
var x, y T // neither are nillable due to ~int
if x != y { // no warning
print("unreachable")
}
}
func noninvoke[T ~func()]() {
var x T
x() // want "nil dereference in dynamic function call"
}
================================================
FILE: go/analysis/passes/pkgfact/pkgfact.go
================================================
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// The pkgfact package is a demonstration and test of the package fact
// mechanism.
//
// The output of the pkgfact analysis is a set of key/values pairs
// gathered from the analyzed package and its imported dependencies.
// Each key/value pair comes from a top-level constant declaration
// whose name starts and ends with "_". For example:
//
// package p
//
// const _greeting_ = "hello"
// const _audience_ = "world"
//
// the pkgfact analysis output for package p would be:
//
// {"greeting": "hello", "audience": "world"}.
//
// In addition, the analysis reports a diagnostic at each import
// showing which key/value pairs it contributes.
package pkgfact
import (
"fmt"
"go/ast"
"go/token"
"go/types"
"reflect"
"sort"
"strings"
"golang.org/x/tools/go/analysis"
)
var Analyzer = &analysis.Analyzer{
Name: "pkgfact",
Doc: "gather name/value pairs from constant declarations",
URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/pkgfact",
Run: run,
FactTypes: []analysis.Fact{new(pairsFact)},
ResultType: reflect.TypeFor[map[string]string](),
}
// A pairsFact is a package-level fact that records
// a set of key=value strings accumulated from constant
// declarations in this package and its dependencies.
// Elements are ordered by keys, which are unique.
type pairsFact []string
func (f *pairsFact) AFact() {}
func (f *pairsFact) String() string { return "pairs(" + strings.Join(*f, ", ") + ")" }
func run(pass *analysis.Pass) (any, error) {
result := make(map[string]string)
// At each import, print the fact from the imported
// package and accumulate its information into the result.
// (Warning: accumulation leads to quadratic growth of work.)
doImport := func(spec *ast.ImportSpec) {
pkg := imported(pass.TypesInfo, spec)
var fact pairsFact
if pass.ImportPackageFact(pkg, &fact) {
for _, pair := range fact {
eq := strings.IndexByte(pair, '=')
result[pair[:eq]] = pair[1+eq:]
}
pass.ReportRangef(spec, "%s", strings.Join(fact, " "))
}
}
// At each "const _name_ = value", add a fact into env.
doConst := func(spec *ast.ValueSpec) {
if len(spec.Names) == len(spec.Values) {
for i := range spec.Names {
name := spec.Names[i].Name
if strings.HasPrefix(name, "_") && strings.HasSuffix(name, "_") {
if key := strings.Trim(name, "_"); key != "" {
value := pass.TypesInfo.Types[spec.Values[i]].Value.String()
result[key] = value
}
}
}
}
}
for _, f := range pass.Files {
for _, decl := range f.Decls {
if decl, ok := decl.(*ast.GenDecl); ok {
for _, spec := range decl.Specs {
switch decl.Tok {
case token.IMPORT:
doImport(spec.(*ast.ImportSpec))
case token.CONST:
doConst(spec.(*ast.ValueSpec))
}
}
}
}
}
// Sort/deduplicate the result and save it as a package fact.
keys := make([]string, 0, len(result))
for key := range result {
keys = append(keys, key)
}
sort.Strings(keys)
var fact pairsFact
for _, key := range keys {
fact = append(fact, fmt.Sprintf("%s=%s", key, result[key]))
}
if len(fact) > 0 {
pass.ExportPackageFact(&fact)
}
return result, nil
}
func imported(info *types.Info, spec *ast.ImportSpec) *types.Package {
obj, ok := info.Implicits[spec]
if !ok {
obj = info.Defs[spec.Name] // renaming import
}
return obj.(*types.PkgName).Imported()
}
================================================
FILE: go/analysis/passes/pkgfact/pkgfact_test.go
================================================
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package pkgfact_test
import (
"testing"
"golang.org/x/tools/go/analysis/analysistest"
"golang.org/x/tools/go/analysis/passes/pkgfact"
)
func Test(t *testing.T) {
testdata := analysistest.TestData()
analysistest.Run(t, testdata, pkgfact.Analyzer, "c")
}
================================================
FILE: go/analysis/passes/pkgfact/testdata/src/a/a.go
================================================
package a
const _greeting_ = "hello"
const _audience_ = "world"
================================================
FILE: go/analysis/passes/pkgfact/testdata/src/b/b.go
================================================
package b
import _ "a"
const _pi_ = 3.14159
================================================
FILE: go/analysis/passes/pkgfact/testdata/src/c/c.go
================================================
// want package:`pairs\(audience="world", greeting="hello", pi=3.14159\)`
package c
import _ "b" // want `audience="world" greeting="hello" pi=3.14159`
================================================
FILE: go/analysis/passes/printf/doc.go
================================================
// Copyright 2023 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package printf defines an Analyzer that checks consistency
// of Printf format strings and arguments.
//
// # Analyzer printf
//
// printf: check consistency of Printf format strings and arguments
//
// The check applies to calls of the formatting functions such as
// [fmt.Printf] and [fmt.Sprintf], as well as any detected wrappers of
// those functions such as [log.Printf]. It reports a variety of
// mistakes such as syntax errors in the format string and mismatches
// (of number and type) between the verbs and their arguments.
//
// See the documentation of the fmt package for the complete set of
// format operators and their operand types.
//
// # Examples
//
// The %d format operator requires an integer operand.
// Here it is incorrectly applied to a string:
//
// fmt.Printf("%d", "hello") // fmt.Printf format %d has arg "hello" of wrong type string
//
// A call to Printf must have as many operands as there are "verbs" in
// the format string, not too few:
//
// fmt.Printf("%d") // fmt.Printf format reads arg 1, but call has 0 args
//
// nor too many:
//
// fmt.Printf("%d", 1, 2) // fmt.Printf call needs 1 arg, but has 2 args
//
// Explicit argument indexes must be no greater than the number of
// arguments:
//
// fmt.Printf("%[3]d", 1, 2) // fmt.Printf call has invalid argument index 3
//
// The checker also uses a heuristic to report calls to Print-like
// functions that appear to have been intended for their Printf-like
// counterpart:
//
// log.Print("%d", 123) // log.Print call has possible formatting directive %d
//
// Conversely, it also reports calls to Printf-like functions with a
// non-constant format string and no other arguments:
//
// fmt.Printf(message) // non-constant format string in call to fmt.Printf
//
// Such calls may have been intended for the function's Print-like
// counterpart: if the value of message happens to contain "%",
// misformatting will occur. In this case, the checker additionally
// suggests a fix to turn the call into:
//
// fmt.Printf("%s", message)
//
// # Inferred printf wrappers
//
// Functions that delegate their arguments to fmt.Printf are
// considered "printf wrappers"; calls to them are subject to the same
// checking. In this example, logf is a printf wrapper:
//
// func logf(level int, format string, args ...any) {
// if enabled(level) {
// log.Printf(format, args...)
// }
// }
//
// logf(3, "invalid request: %v") // logf format reads arg 1, but call has 0 args
//
// To enable printf checking on a function that is not found by this
// analyzer's heuristics (for example, because control is obscured by
// dynamic method calls), insert a bogus call:
//
// func MyPrintf(format string, args ...any) {
// if false {
// _ = fmt.Sprintf(format, args...) // enable printf checking
// }
// ...
// }
//
// A local function may also be inferred as a printf wrapper. If it
// is assigned to a variable, each call made through that variable will
// be checked just like a call to a function:
//
// logf := func(format string, args ...any) {
// message := fmt.Sprintf(format, args...)
// log.Printf("%s: %s", prefix, message)
// }
// logf("%s", 123) // logf format %s has arg 123 of wrong type int
//
// Interface methods may also be analyzed as printf wrappers, if
// within the interface's package there is an assignment from a
// implementation type whose corresponding method is a printf wrapper.
//
// For example, the var declaration below causes a *myLoggerImpl value
// to be assigned to a Logger variable:
//
// type Logger interface {
// Logf(format string, args ...any)
// }
//
// type myLoggerImpl struct{ ... }
//
// var _ Logger = (*myLoggerImpl)(nil)
//
// func (*myLoggerImpl) Logf(format string, args ...any) {
// println(fmt.Sprintf(format, args...))
// }
//
// Since myLoggerImpl's Logf method is a printf wrapper, this
// establishes that Logger.Logf is a printf wrapper too, causing
// dynamic calls through the interface to be checked:
//
// func f(log Logger) {
// log.Logf("%s", 123) // Logger.Logf format %s has arg 123 of wrong type int
// }
//
// This feature applies only to interface methods declared in files
// using at least Go 1.26.
//
// # Specifying printf wrappers by flag
//
// The -funcs flag specifies a comma-separated list of names of
// additional known formatting functions or methods. (This legacy flag
// is rarely used due to the automatic inference described above.)
//
// If the name contains a period, it must denote a specific function
// using one of the following forms:
//
// dir/pkg.Function
// dir/pkg.Type.Method
// (*dir/pkg.Type).Method
//
// Otherwise the name is interpreted as a case-insensitive unqualified
// identifier such as "errorf". Either way, if a listed name ends in f, the
// function is assumed to be Printf-like, taking a format string before the
// argument list. Otherwise it is assumed to be Print-like, taking a list
// of arguments with no format string.
package printf
================================================
FILE: go/analysis/passes/printf/main.go
================================================
// Copyright 2024 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build ignore
// The printf command applies the printf checker to the specified
// packages of Go source code.
//
// Run with:
//
// $ go run ./go/analysis/passes/printf/main.go -- packages...
package main
import (
"golang.org/x/tools/go/analysis/passes/printf"
"golang.org/x/tools/go/analysis/singlechecker"
)
func main() { singlechecker.Main(printf.Analyzer) }
================================================
FILE: go/analysis/passes/printf/printf.go
================================================
// Copyright 2010 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package printf
import (
_ "embed"
"fmt"
"go/ast"
"go/constant"
"go/token"
"go/types"
"reflect"
"regexp"
"sort"
"strings"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/ast/edge"
"golang.org/x/tools/go/ast/inspector"
"golang.org/x/tools/go/types/typeutil"
"golang.org/x/tools/internal/analysis/analyzerutil"
"golang.org/x/tools/internal/astutil"
"golang.org/x/tools/internal/fmtstr"
"golang.org/x/tools/internal/typeparams"
"golang.org/x/tools/internal/typesinternal"
"golang.org/x/tools/internal/versions"
"golang.org/x/tools/refactor/satisfy"
)
func init() {
Analyzer.Flags.Var(isPrint, "funcs", "comma-separated list of print function names to check")
}
//go:embed doc.go
var doc string
var Analyzer = &analysis.Analyzer{
Name: "printf",
Doc: analyzerutil.MustExtractDoc(doc, "printf"),
URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/printf",
Requires: []*analysis.Analyzer{inspect.Analyzer},
Run: run,
ResultType: reflect.TypeFor[*Result](),
FactTypes: []analysis.Fact{new(isWrapper)},
}
// Kind is a kind of fmt function behavior.
type Kind int
const (
KindNone Kind = iota // not a fmt wrapper function
KindPrint // function behaves like fmt.Print
KindPrintf // function behaves like fmt.Printf
KindErrorf // function behaves like fmt.Errorf
)
func (kind Kind) String() string {
switch kind {
case KindPrint:
return "print"
case KindPrintf:
return "printf"
case KindErrorf:
return "errorf"
}
return "(none)"
}
// Result is the printf analyzer's result type. Clients may query the result
// to learn whether a function behaves like fmt.Print or fmt.Printf.
type Result struct {
funcs map[types.Object]Kind
}
// Kind reports whether fn behaves like fmt.Print or fmt.Printf.
func (r *Result) Kind(fn *types.Func) Kind {
_, ok := isPrint[fn.FullName()]
if !ok {
// Next look up just "printf", for use with -printf.funcs.
_, ok = isPrint[strings.ToLower(fn.Name())]
}
if ok {
if strings.HasSuffix(fn.Name(), "f") {
return KindPrintf
} else {
return KindPrint
}
}
return r.funcs[fn]
}
// isWrapper is a fact indicating that a function is a print or printf wrapper.
type isWrapper struct{ Kind Kind }
func (f *isWrapper) AFact() {}
func (f *isWrapper) String() string {
switch f.Kind {
case KindPrintf:
return "printfWrapper"
case KindPrint:
return "printWrapper"
case KindErrorf:
return "errorfWrapper"
default:
return "unknownWrapper"
}
}
func run(pass *analysis.Pass) (any, error) {
res := &Result{
funcs: make(map[types.Object]Kind),
}
findPrintLike(pass, res)
checkCalls(pass, res)
return res, nil
}
// A wrapper is a candidate print/printf wrapper function.
//
// We represent functions generally as types.Object, not *Func, so
// that we can analyze anonymous functions such as
//
// printf := func(format string, args ...any) {...},
//
// representing them by the *types.Var symbol for the local variable
// 'printf'.
type wrapper struct {
obj types.Object // *Func or *Var
curBody inspector.Cursor // for *ast.BlockStmt
format *types.Var // optional "format string" parameter in the Func{Decl,Lit}
args *types.Var // "args ...any" parameter in the Func{Decl,Lit}
callers []printfCaller
}
// printfCaller is a candidate print{,f} forwarding call from candidate wrapper w.
type printfCaller struct {
w *wrapper
call *ast.CallExpr // forwarding call (nil for implicit interface method -> impl calls)
}
// formatArgsParams returns the "format string" and "args ...any"
// parameters of a potential print or printf wrapper function.
// (The format is nil in the print-like case.)
func formatArgsParams(sig *types.Signature) (format, args *types.Var) {
if !sig.Variadic() {
return nil, nil // not variadic
}
params := sig.Params()
nparams := params.Len() // variadic => nonzero
// Is second last param 'format string'?
if nparams >= 2 {
if p := params.At(nparams - 2); p.Type() == types.Typ[types.String] {
format = p
}
}
// Check final parameter is "args ...any".
// (variadic => slice)
args = params.At(nparams - 1)
iface, ok := types.Unalias(args.Type().(*types.Slice).Elem()).(*types.Interface)
if !ok || !iface.Empty() {
return nil, nil
}
return format, args
}
// findPrintLike scans the entire package to find print or printf-like functions.
// When it returns, all such functions have been identified.
func findPrintLike(pass *analysis.Pass, res *Result) {
var (
inspect = pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
info = pass.TypesInfo
)
// Pass 1: gather candidate wrapper functions (and populate wrappers).
var (
wrappers []*wrapper
byObj = make(map[types.Object]*wrapper)
)
for cur := range inspect.Root().Preorder((*ast.FuncDecl)(nil), (*ast.FuncLit)(nil), (*ast.InterfaceType)(nil)) {
// addWrapper records that a func (or var representing
// a FuncLit) is a potential print{,f} wrapper.
// curBody is its *ast.BlockStmt, if any.
addWrapper := func(obj types.Object, sig *types.Signature, curBody inspector.Cursor) *wrapper {
format, args := formatArgsParams(sig)
if args != nil {
// obj (the symbol for a function/method, or variable
// assigned to an anonymous function) is a potential
// print or printf wrapper.
//
// Later processing will analyze the graph of potential
// wrappers and their function bodies to pick out the
// ones that are true wrappers.
w := &wrapper{
obj: obj,
curBody: curBody,
format: format, // non-nil => printf
args: args,
}
byObj[w.obj] = w
wrappers = append(wrappers, w)
return w
}
return nil
}
switch f := cur.Node().(type) {
case *ast.FuncDecl:
// named function or method:
//
// func wrapf(format string, args ...any) {...}
if f.Body != nil {
fn := info.Defs[f.Name].(*types.Func)
addWrapper(fn, fn.Signature(), cur.ChildAt(edge.FuncDecl_Body, -1))
}
case *ast.FuncLit:
// anonymous function directly assigned to a variable:
//
// var wrapf = func(format string, args ...any) {...}
// wrapf := func(format string, args ...any) {...}
// wrapf = func(format string, args ...any) {...}
//
// The LHS may also be a struct field x.wrapf or
// an imported var pkg.Wrapf.
//
var lhs ast.Expr
switch ek, idx := cur.ParentEdge(); ek {
case edge.ValueSpec_Values:
curName := cur.Parent().ChildAt(edge.ValueSpec_Names, idx)
lhs = curName.Node().(*ast.Ident)
case edge.AssignStmt_Rhs:
curLhs := cur.Parent().ChildAt(edge.AssignStmt_Lhs, idx)
lhs = curLhs.Node().(ast.Expr)
}
var v *types.Var
switch lhs := lhs.(type) {
case *ast.Ident:
// variable: wrapf = func(...)
v, _ = info.ObjectOf(lhs).(*types.Var)
case *ast.SelectorExpr:
if sel, ok := info.Selections[lhs]; ok {
// struct field: x.wrapf = func(...)
v = sel.Obj().(*types.Var)
} else {
// imported var: pkg.Wrapf = func(...)
v = info.Uses[lhs.Sel].(*types.Var)
}
}
if v != nil {
sig := info.TypeOf(f).(*types.Signature)
curBody := cur.ChildAt(edge.FuncLit_Body, -1)
addWrapper(v, sig, curBody)
}
case *ast.InterfaceType:
// Induction through interface methods is gated as
// if it were a go1.26 language feature, to avoid
// surprises when go test's vet suite gets stricter.
if analyzerutil.FileUsesGoVersion(pass, astutil.EnclosingFile(cur), versions.Go1_26) {
for imeth := range info.TypeOf(f).(*types.Interface).Methods() {
addWrapper(imeth, imeth.Signature(), inspector.Cursor{})
}
}
}
}
// impls maps abstract methods to implementations.
//
// Interface methods are modelled as if they have a body
// that calls each implementing method.
//
// In the code below, impls maps Logger.Logf to
// [myLogger.Logf], and if myLogger.Logf is discovered to be
// printf-like, then so will be Logger.Logf.
//
// type Logger interface {
// Logf(format string, args ...any)
// }
// type myLogger struct{ ... }
// func (myLogger) Logf(format string, args ...any) {...}
// var _ Logger = myLogger{}
impls := methodImplementations(pass)
// doCall records a call from one wrapper to another.
doCall := func(w *wrapper, callee types.Object, call *ast.CallExpr) {
// Call from one wrapper candidate to another?
// Record the edge so that if callee is found to be
// a true wrapper, w will be too.
if w2, ok := byObj[callee]; ok {
w2.callers = append(w2.callers, printfCaller{w, call})
}
// Is the candidate a true wrapper, because it calls
// a known print{,f}-like function from the allowlist
// or an imported fact, or another wrapper found
// to be a true wrapper?
// If so, convert all w's callers to kind.
kind := callKind(pass, callee, res)
if kind != KindNone {
propagate(pass, w, call, kind, res)
}
}
// Pass 2: scan the body of each wrapper function
// for calls to other printf-like functions.
for _, w := range wrappers {
// An interface method has no body, but acts
// like an implicit call to each implementing method.
if !w.curBody.Valid() {
for impl := range impls[w.obj.(*types.Func)] {
doCall(w, impl, nil)
}
continue // (no body)
}
// Process all calls in the wrapper function's body.
scan:
for cur := range w.curBody.Preorder(
(*ast.AssignStmt)(nil),
(*ast.UnaryExpr)(nil),
(*ast.CallExpr)(nil),
) {
switch n := cur.Node().(type) {
// Reject tricky cases where the parameters
// are potentially mutated by AssignStmt or UnaryExpr.
// (This logic checks for mutation only before the call.)
// TODO: Relax these checks; issue 26555.
case *ast.AssignStmt:
// If the wrapper updates format or args
// it is not a simple wrapper.
for _, lhs := range n.Lhs {
if w.format != nil && match(info, lhs, w.format) ||
match(info, lhs, w.args) {
break scan
}
}
case *ast.UnaryExpr:
// If the wrapper computes &format or &args,
// it is not a simple wrapper.
if n.Op == token.AND &&
(w.format != nil && match(info, n.X, w.format) ||
match(info, n.X, w.args)) {
break scan
}
case *ast.CallExpr:
if len(n.Args) > 0 && match(info, n.Args[len(n.Args)-1], w.args) {
if callee := typeutil.Callee(pass.TypesInfo, n); callee != nil {
doCall(w, callee, n)
}
}
}
}
}
}
// methodImplementations returns the mapping from interface methods
// declared in this package to their corresponding implementing
// methods (which may also be interface methods), according to the set
// of assignments to interface types that appear within this package.
func methodImplementations(pass *analysis.Pass) map[*types.Func]map[*types.Func]bool {
impls := make(map[*types.Func]map[*types.Func]bool)
// To find interface/implementation relations,
// we use the 'satisfy' pass, but proposal #70638
// provides a better way.
//
// This pass over the syntax could be factored out as
// a separate analysis pass if it is needed by other
// analyzers.
var f satisfy.Finder
f.Find(pass.TypesInfo, pass.Files)
for assign := range f.Result {
// Have: LHS = RHS, where LHS is an interface type.
for imeth := range assign.LHS.Underlying().(*types.Interface).Methods() {
// Limit to interface methods of current package.
if imeth.Pkg() != pass.Pkg {
continue
}
if _, args := formatArgsParams(imeth.Signature()); args == nil {
continue // not print{,f}-like
}
// Add implementing method to the set.
impl, _, _ := types.LookupFieldOrMethod(assign.RHS, false, pass.Pkg, imeth.Name()) // can't fail
set, ok := impls[imeth]
if !ok {
set = make(map[*types.Func]bool)
impls[imeth] = set
}
set[impl.(*types.Func)] = true
}
}
return impls
}
func match(info *types.Info, arg ast.Expr, param *types.Var) bool {
id, ok := arg.(*ast.Ident)
return ok && info.ObjectOf(id) == param
}
// propagate propagates changes in wrapper (non-None) kind information backwards
// through through the wrapper.callers graph of well-formed forwarding calls.
func propagate(pass *analysis.Pass, w *wrapper, call *ast.CallExpr, kind Kind, res *Result) {
// Check correct call forwarding.
//
// Interface methods (call==nil) forward
// correctly by construction.
if call != nil && !checkForward(pass, w, call, kind) {
return
}
// If the candidate's print{,f} status becomes known,
// propagate it back to all its so-far known callers.
if res.funcs[w.obj] != kind {
res.funcs[w.obj] = kind
// Export a fact.
// (This is a no-op for local symbols.)
// We can't export facts on a symbol of another package,
// but we can treat the symbol as a wrapper within
// the current analysis unit.
if w.obj.Pkg() == pass.Pkg {
// Facts are associated with origins.
pass.ExportObjectFact(origin(w.obj), &isWrapper{Kind: kind})
}
// Propagate kind back to known callers.
for _, caller := range w.callers {
propagate(pass, caller.w, caller.call, kind, res)
}
}
}
// checkForward checks whether a call from wrapper w is a well-formed
// forwarding call of the specified (non-None) kind.
//
// If not, it reports a diagnostic that the user wrote
// fmt.Printf(format, args) instead of fmt.Printf(format, args...).
func checkForward(pass *analysis.Pass, w *wrapper, call *ast.CallExpr, kind Kind) bool {
// Printf/Errorf calls must delegate the format string.
switch kind {
case KindPrintf, KindErrorf:
if len(call.Args) < 2 || !match(pass.TypesInfo, call.Args[len(call.Args)-2], w.format) {
return false
}
}
// The args... delegation must be variadic.
// (That args is actually delegated was
// established before the root call to doCall.)
if !call.Ellipsis.IsValid() {
typ, ok := pass.TypesInfo.Types[call.Fun].Type.(*types.Signature)
if !ok {
return false
}
if len(call.Args) > typ.Params().Len() {
// If we're passing more arguments than what the
// print/printf function can take, adding an ellipsis
// would break the program. For example:
//
// func foo(arg1 string, arg2 ...interface{}) {
// fmt.Printf("%s %v", arg1, arg2)
// }
return false
}
pass.ReportRangef(call, "missing ... in args forwarded to %s-like function", kind)
return false
}
return true
}
func origin(obj types.Object) types.Object {
switch obj := obj.(type) {
case *types.Func:
return obj.Origin()
case *types.Var:
return obj.Origin()
}
return obj
}
// isPrint records the print functions.
// If a key ends in 'f' then it is assumed to be a formatted print.
//
// Keys are either values returned by (*types.Func).FullName,
// or case-insensitive identifiers such as "errorf".
//
// The -funcs flag adds to this set.
//
// The set below includes facts for many important standard library
// functions, even though the analysis is capable of deducing that, for
// example, fmt.Printf forwards to fmt.Fprintf. We avoid relying on the
// driver applying analyzers to standard packages because "go vet" does
// not do so with gccgo, and nor do some other build systems.
var isPrint = stringSet{
"fmt.Appendf": true,
"fmt.Append": true,
"fmt.Appendln": true,
"fmt.Errorf": true,
"fmt.Fprint": true,
"fmt.Fprintf": true,
"fmt.Fprintln": true,
"fmt.Print": true,
"fmt.Printf": true,
"fmt.Println": true,
"fmt.Sprint": true,
"fmt.Sprintf": true,
"fmt.Sprintln": true,
"runtime/trace.Logf": true,
"log.Print": true,
"log.Printf": true,
"log.Println": true,
"log.Fatal": true,
"log.Fatalf": true,
"log.Fatalln": true,
"log.Panic": true,
"log.Panicf": true,
"log.Panicln": true,
"(*log.Logger).Fatal": true,
"(*log.Logger).Fatalf": true,
"(*log.Logger).Fatalln": true,
"(*log.Logger).Panic": true,
"(*log.Logger).Panicf": true,
"(*log.Logger).Panicln": true,
"(*log.Logger).Print": true,
"(*log.Logger).Printf": true,
"(*log.Logger).Println": true,
"(*testing.common).Error": true,
"(*testing.common).Errorf": true,
"(*testing.common).Fatal": true,
"(*testing.common).Fatalf": true,
"(*testing.common).Log": true,
"(*testing.common).Logf": true,
"(*testing.common).Skip": true,
"(*testing.common).Skipf": true,
"(testing.TB).Error": true,
"(testing.TB).Errorf": true,
"(testing.TB).Fatal": true,
"(testing.TB).Fatalf": true,
"(testing.TB).Log": true,
"(testing.TB).Logf": true,
"(testing.TB).Skip": true,
"(testing.TB).Skipf": true,
}
// formatStringIndex returns the index of the format string (the last
// non-variadic parameter) within the given printf-like call
// expression, or -1 if unknown.
func formatStringIndex(pass *analysis.Pass, call *ast.CallExpr) int {
typ := pass.TypesInfo.Types[call.Fun].Type
if typ == nil {
return -1 // missing type
}
sig, ok := typ.(*types.Signature)
if !ok {
return -1 // ill-typed
}
if !sig.Variadic() {
// Skip checking non-variadic functions.
return -1
}
idx := sig.Params().Len() - 2
if idx < 0 {
// Skip checking variadic functions without
// fixed arguments.
return -1
}
return idx
}
// stringConstantExpr returns expression's string constant value.
//
// ("", false) is returned if expression isn't a string
// constant.
func stringConstantExpr(pass *analysis.Pass, expr ast.Expr) (string, bool) {
lit := pass.TypesInfo.Types[expr].Value
if lit != nil && lit.Kind() == constant.String {
return constant.StringVal(lit), true
}
return "", false
}
// checkCalls triggers the print-specific checks for calls that invoke a print
// function.
func checkCalls(pass *analysis.Pass, res *Result) {
inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
nodeFilter := []ast.Node{
(*ast.File)(nil),
(*ast.CallExpr)(nil),
}
var fileVersion string // for selectively suppressing checks; "" if unknown.
inspect.Preorder(nodeFilter, func(n ast.Node) {
switch n := n.(type) {
case *ast.File:
fileVersion = versions.Lang(versions.FileVersion(pass.TypesInfo, n))
case *ast.CallExpr:
if callee := typeutil.Callee(pass.TypesInfo, n); callee != nil {
kind := callKind(pass, callee, res)
switch kind {
case KindPrintf, KindErrorf:
checkPrintf(pass, fileVersion, kind, n, fullname(callee))
case KindPrint:
checkPrint(pass, n, fullname(callee))
}
}
}
})
}
func fullname(obj types.Object) string {
if fn, ok := obj.(*types.Func); ok {
return fn.FullName()
}
return obj.Name()
}
// callKind returns the symbol of the called function
// and its print/printf kind, if any.
// (The symbol may be a var for an anonymous function.)
// The result is memoized in res.funcs.
func callKind(pass *analysis.Pass, obj types.Object, res *Result) Kind {
kind, ok := res.funcs[obj]
if !ok {
// cache miss
_, ok := isPrint[fullname(obj)]
if !ok {
// Next look up just "printf", for use with -printf.funcs.
_, ok = isPrint[strings.ToLower(obj.Name())]
}
if ok {
// well-known printf functions
if fullname(obj) == "fmt.Errorf" {
kind = KindErrorf
} else if strings.HasSuffix(obj.Name(), "f") {
kind = KindPrintf
} else {
kind = KindPrint
}
} else {
// imported wrappers
// Facts are associated with generic declarations, not instantiations.
obj = origin(obj)
var fact isWrapper
if pass.ImportObjectFact(obj, &fact) {
kind = fact.Kind
}
}
res.funcs[obj] = kind // cache
}
return kind
}
// isFormatter reports whether t could satisfy fmt.Formatter.
// The only interface method to look for is "Format(State, rune)".
func isFormatter(typ types.Type) bool {
// If the type is an interface, the value it holds might satisfy fmt.Formatter.
if _, ok := typ.Underlying().(*types.Interface); ok {
// Don't assume type parameters could be formatters. With the greater
// expressiveness of constraint interface syntax we expect more type safety
// when using type parameters.
if !typeparams.IsTypeParam(typ) {
return true
}
}
obj, _, _ := types.LookupFieldOrMethod(typ, false, nil, "Format")
fn, ok := obj.(*types.Func)
if !ok {
return false
}
sig := fn.Type().(*types.Signature)
return sig.Params().Len() == 2 &&
sig.Results().Len() == 0 &&
typesinternal.IsTypeNamed(sig.Params().At(0).Type(), "fmt", "State") &&
types.Identical(sig.Params().At(1).Type(), types.Typ[types.Rune])
}
// checkPrintf checks a call to a formatted print routine such as Printf.
func checkPrintf(pass *analysis.Pass, fileVersion string, kind Kind, call *ast.CallExpr, name string) {
idx := formatStringIndex(pass, call)
if idx < 0 || idx >= len(call.Args) {
return
}
formatArg := call.Args[idx]
format, ok := stringConstantExpr(pass, formatArg)
if !ok {
// Format string argument is non-constant.
// It is a common mistake to call fmt.Printf(msg) with a
// non-constant format string and no arguments:
// if msg contains "%", misformatting occurs.
// Report the problem and suggest a fix: fmt.Printf("%s", msg).
//
// However, as described in golang/go#71485, this analysis can produce a
// significant number of diagnostics in existing code, and the bugs it
// finds are sometimes unlikely or inconsequential, and may not be worth
// fixing for some users. Gating on language version allows us to avoid
// breaking existing tests and CI scripts.
if idx == len(call.Args)-1 &&
fileVersion != "" && // fail open
versions.AtLeast(fileVersion, versions.Go1_24) {
pass.Report(analysis.Diagnostic{
Pos: formatArg.Pos(),
End: formatArg.End(),
Message: fmt.Sprintf("non-constant format string in call to %s",
name),
SuggestedFixes: []analysis.SuggestedFix{{
Message: `Insert "%s" format string`,
TextEdits: []analysis.TextEdit{{
Pos: formatArg.Pos(),
End: formatArg.Pos(),
NewText: []byte(`"%s", `),
}},
}},
})
}
return
}
firstArg := idx + 1 // Arguments are immediately after format string.
if !strings.Contains(format, "%") {
if len(call.Args) > firstArg {
pass.ReportRangef(call.Args[firstArg], "%s call has arguments but no formatting directives", name)
}
return
}
// Pass the string constant value so
// fmt.Sprintf("%"+("s"), "hi", 3) can be reported as
// "fmt.Sprintf call needs 1 arg but has 2 args".
operations, err := fmtstr.Parse(format, idx)
if err != nil {
// All error messages are in predicate form ("call has a problem")
// so that they may be affixed into a subject ("log.Printf ").
pass.ReportRangef(formatArg, "%s %s", name, err)
return
}
// index of the highest used index.
maxArgIndex := firstArg - 1
anyIndex := false
// Check formats against args.
for _, op := range operations {
if op.Prec.Index != -1 ||
op.Width.Index != -1 ||
op.Verb.Index != -1 {
anyIndex = true
}
rng := opRange(formatArg, op)
if !okPrintfArg(pass, fileVersion, call, rng, &maxArgIndex, firstArg, name, op) {
// One error per format is enough.
return
}
if op.Verb.Verb == 'w' {
switch kind {
case KindNone, KindPrint, KindPrintf:
pass.ReportRangef(rng, "%s does not support error-wrapping directive %%w", name)
return
}
}
}
// Dotdotdot is hard.
if call.Ellipsis.IsValid() && maxArgIndex >= len(call.Args)-2 {
return
}
// If any formats are indexed, extra arguments are ignored.
if anyIndex {
return
}
// There should be no leftover arguments.
if maxArgIndex+1 < len(call.Args) {
expect := maxArgIndex + 1 - firstArg
numArgs := len(call.Args) - firstArg
pass.ReportRangef(call, "%s call needs %v but has %v", name, count(expect, "arg"), count(numArgs, "arg"))
}
}
// opRange returns the source range for the specified printf operation,
// such as the position of the %v substring of "...%v...".
func opRange(formatArg ast.Expr, op *fmtstr.Operation) analysis.Range {
if lit, ok := formatArg.(*ast.BasicLit); ok {
rng, err := astutil.RangeInStringLiteral(lit, op.Range.Start, op.Range.End)
if err == nil {
return rng // position of "%v"
}
}
return formatArg // entire format string
}
// printfArgType encodes the types of expressions a printf verb accepts. It is a bitmask.
type printfArgType int
const (
argBool printfArgType = 1 << iota
argByte
argInt
argRune
argString
argFloat
argComplex
argPointer
argError
anyType printfArgType = ^0
)
type printVerb struct {
verb rune // User may provide verb through Formatter; could be a rune.
flags string // known flags are all ASCII
typ printfArgType
}
// Common flag sets for printf verbs.
const (
noFlag = ""
numFlag = " -+.0"
sharpNumFlag = " -+.0#"
allFlags = " -+.0#"
)
// printVerbs identifies which flags are known to printf for each verb.
var printVerbs = []printVerb{
// '-' is a width modifier, always valid.
// '.' is a precision for float, max width for strings.
// '+' is required sign for numbers, Go format for %v.
// '#' is alternate format for several verbs.
// ' ' is spacer for numbers
{'%', noFlag, 0},
{'b', sharpNumFlag, argInt | argFloat | argComplex | argPointer},
{'c', "-", argRune | argInt},
{'d', numFlag, argInt | argPointer},
{'e', sharpNumFlag, argFloat | argComplex},
{'E', sharpNumFlag, argFloat | argComplex},
{'f', sharpNumFlag, argFloat | argComplex},
{'F', sharpNumFlag, argFloat | argComplex},
{'g', sharpNumFlag, argFloat | argComplex},
{'G', sharpNumFlag, argFloat | argComplex},
{'o', sharpNumFlag, argInt | argPointer},
{'O', sharpNumFlag, argInt | argPointer},
{'p', "-#", argPointer},
{'q', " -+.0#", argRune | argInt | argString}, // note: when analyzing go1.26 code, argInt => argByte
{'s', " -+.0", argString},
{'t', "-", argBool},
{'T', "-", anyType},
{'U', "-#", argRune | argInt},
{'v', allFlags, anyType},
{'w', allFlags, argError},
{'x', sharpNumFlag, argRune | argInt | argString | argPointer | argFloat | argComplex},
{'X', sharpNumFlag, argRune | argInt | argString | argPointer | argFloat | argComplex},
}
// okPrintfArg compares the operation to the arguments actually present,
// reporting any discrepancies it can discern, maxArgIndex was the index of the highest used index.
// If the final argument is ellipsissed, there's little it can do for that.
func okPrintfArg(pass *analysis.Pass, fileVersion string, call *ast.CallExpr, rng analysis.Range, maxArgIndex *int, firstArg int, name string, operation *fmtstr.Operation) (ok bool) {
verb := operation.Verb.Verb
var v printVerb
found := false
// Linear scan is fast enough for a small list.
for _, v = range printVerbs {
if v.verb == verb {
found = true
break
}
}
// When analyzing go1.26 code, rune and byte are the only %q integers (#72850).
if verb == 'q' &&
fileVersion != "" && // fail open
versions.AtLeast(fileVersion, versions.Go1_26) {
v.typ = argRune | argByte | argString
}
// Could verb's arg implement fmt.Formatter?
// Skip check for the %w verb, which requires an error.
formatter := false
if v.typ != argError && operation.Verb.ArgIndex < len(call.Args) {
if tv, ok := pass.TypesInfo.Types[call.Args[operation.Verb.ArgIndex]]; ok {
formatter = isFormatter(tv.Type)
}
}
if !formatter {
if !found {
pass.ReportRangef(rng, "%s format %s has unknown verb %c", name, operation.Text, verb)
return false
}
for _, flag := range operation.Flags {
// TODO: Disable complaint about '0' for Go 1.10. To be fixed properly in 1.11.
// See issues 23598 and 23605.
if flag == '0' {
continue
}
if !strings.ContainsRune(v.flags, rune(flag)) {
pass.ReportRangef(rng, "%s format %s has unrecognized flag %c", name, operation.Text, flag)
return false
}
}
}
var argIndexes []int
// First check for *.
if operation.Width.Dynamic != -1 {
argIndexes = append(argIndexes, operation.Width.Dynamic)
}
if operation.Prec.Dynamic != -1 {
argIndexes = append(argIndexes, operation.Prec.Dynamic)
}
// If len(argIndexes)>0, we have something like %.*s and all
// indexes in argIndexes must be an integer.
for _, argIndex := range argIndexes {
if !argCanBeChecked(pass, call, rng, argIndex, firstArg, operation, name) {
return
}
arg := call.Args[argIndex]
if reason, ok := matchArgType(pass, argInt, arg); !ok {
details := ""
if reason != "" {
details = " (" + reason + ")"
}
pass.ReportRangef(rng, "%s format %s uses non-int %s%s as argument of *", name, operation.Text, astutil.Format(pass.Fset, arg), details)
return false
}
}
// Collect to update maxArgNum in one loop.
if operation.Verb.ArgIndex != -1 && verb != '%' {
argIndexes = append(argIndexes, operation.Verb.ArgIndex)
}
for _, index := range argIndexes {
*maxArgIndex = max(*maxArgIndex, index)
}
// Special case for '%', go will print "fmt.Printf("%10.2%%dhello", 4)"
// as "%4hello", discard any runes between the two '%'s, and treat the verb '%'
// as an ordinary rune, so early return to skip the type check.
if verb == '%' || formatter {
return true
}
// Now check verb's type.
verbArgIndex := operation.Verb.ArgIndex
if !argCanBeChecked(pass, call, rng, verbArgIndex, firstArg, operation, name) {
return false
}
arg := call.Args[verbArgIndex]
if isFunctionValue(pass, arg) && verb != 'p' && verb != 'T' {
pass.ReportRangef(rng, "%s format %s arg %s is a func value, not called", name, operation.Text, astutil.Format(pass.Fset, arg))
return false
}
if reason, ok := matchArgType(pass, v.typ, arg); !ok {
typeString := ""
if typ := pass.TypesInfo.Types[arg].Type; typ != nil {
typeString = typ.String()
}
details := ""
if reason != "" {
details = " (" + reason + ")"
}
pass.ReportRangef(rng, "%s format %s has arg %s of wrong type %s%s", name, operation.Text, astutil.Format(pass.Fset, arg), typeString, details)
return false
}
// Detect recursive formatting via value's String/Error methods.
// The '#' flag suppresses the methods, except with %x, %X, and %q.
if v.typ&argString != 0 && v.verb != 'T' && (!strings.Contains(operation.Flags, "#") || strings.ContainsRune("qxX", v.verb)) {
if methodName, ok := recursiveStringer(pass, arg); ok {
pass.ReportRangef(rng, "%s format %s with arg %s causes recursive %s method call", name, operation.Text, astutil.Format(pass.Fset, arg), methodName)
return false
}
}
return true
}
// recursiveStringer reports whether the argument e is a potential
// recursive call to stringer or is an error, such as t and &t in these examples:
//
// func (t *T) String() string { printf("%s", t) }
// func (t T) Error() string { printf("%s", t) }
// func (t T) String() string { printf("%s", &t) }
func recursiveStringer(pass *analysis.Pass, e ast.Expr) (string, bool) {
typ := pass.TypesInfo.Types[e].Type
// It's unlikely to be a recursive stringer if it has a Format method.
if isFormatter(typ) {
return "", false
}
// Does e allow e.String() or e.Error()?
strObj, _, _ := types.LookupFieldOrMethod(typ, false, pass.Pkg, "String")
strMethod, strOk := strObj.(*types.Func)
errObj, _, _ := types.LookupFieldOrMethod(typ, false, pass.Pkg, "Error")
errMethod, errOk := errObj.(*types.Func)
if !strOk && !errOk {
return "", false
}
// inScope returns true if e is in the scope of f.
inScope := func(e ast.Expr, f *types.Func) bool {
return f.Scope() != nil && f.Scope().Contains(e.Pos())
}
// Is the expression e within the body of that String or Error method?
var method *types.Func
if strOk && strMethod.Pkg() == pass.Pkg && inScope(e, strMethod) {
method = strMethod
} else if errOk && errMethod.Pkg() == pass.Pkg && inScope(e, errMethod) {
method = errMethod
} else {
return "", false
}
sig := method.Type().(*types.Signature)
if !isStringer(sig) {
return "", false
}
// Is it the receiver r, or &r?
if u, ok := e.(*ast.UnaryExpr); ok && u.Op == token.AND {
e = u.X // strip off & from &r
}
if id, ok := e.(*ast.Ident); ok {
if pass.TypesInfo.Uses[id] == sig.Recv() {
return method.FullName(), true
}
}
return "", false
}
// isStringer reports whether the method signature matches the String() definition in fmt.Stringer.
func isStringer(sig *types.Signature) bool {
return sig.Params().Len() == 0 &&
sig.Results().Len() == 1 &&
sig.Results().At(0).Type() == types.Typ[types.String]
}
// isFunctionValue reports whether the expression is a function as opposed to a function call.
// It is almost always a mistake to print a function value.
func isFunctionValue(pass *analysis.Pass, e ast.Expr) bool {
if typ := pass.TypesInfo.Types[e].Type; typ != nil {
// Don't call Underlying: a named func type with a String method is ok.
// TODO(adonovan): it would be more precise to check isStringer.
_, ok := typ.(*types.Signature)
return ok
}
return false
}
// argCanBeChecked reports whether the specified argument is statically present;
// it may be beyond the list of arguments or in a terminal slice... argument, which
// means we can't see it.
func argCanBeChecked(pass *analysis.Pass, call *ast.CallExpr, rng analysis.Range, argIndex, firstArg int, operation *fmtstr.Operation, name string) bool {
if argIndex <= 0 {
// Shouldn't happen, so catch it with prejudice.
panic("negative argIndex")
}
if argIndex < len(call.Args)-1 {
return true // Always OK.
}
if call.Ellipsis.IsValid() {
return false // We just can't tell; there could be many more arguments.
}
if argIndex < len(call.Args) {
return true
}
// There are bad indexes in the format or there are fewer arguments than the format needs.
// This is the argument number relative to the format: Printf("%s", "hi") will give 1 for the "hi".
arg := argIndex - firstArg + 1 // People think of arguments as 1-indexed.
pass.ReportRangef(rng, "%s format %s reads arg #%d, but call has %v", name, operation.Text, arg, count(len(call.Args)-firstArg, "arg"))
return false
}
// printFormatRE is the regexp we match and report as a possible format string
// in the first argument to unformatted prints like fmt.Print.
// We exclude the space flag, so that printing a string like "x % y" is not reported as a format.
var printFormatRE = regexp.MustCompile(`%` + flagsRE + numOptRE + `\.?` + numOptRE + indexOptRE + verbRE)
const (
flagsRE = `[+\-#]*`
indexOptRE = `(\[[0-9]+\])?`
numOptRE = `([0-9]+|` + indexOptRE + `\*)?`
verbRE = `[bcdefgopqstvxEFGTUX]`
)
// checkPrint checks a call to an unformatted print routine such as Println.
func checkPrint(pass *analysis.Pass, call *ast.CallExpr, name string) {
firstArg := 0
typ := pass.TypesInfo.Types[call.Fun].Type
if typ == nil {
// Skip checking functions with unknown type.
return
}
if sig, ok := typ.Underlying().(*types.Signature); ok {
if !sig.Variadic() {
// Skip checking non-variadic functions.
return
}
params := sig.Params()
firstArg = params.Len() - 1
typ := params.At(firstArg).Type()
typ = typ.(*types.Slice).Elem()
it, ok := types.Unalias(typ).(*types.Interface)
if !ok || !it.Empty() {
// Skip variadic functions accepting non-interface{} args.
return
}
}
args := call.Args
if len(args) <= firstArg {
// Skip calls without variadic args.
return
}
args = args[firstArg:]
if firstArg == 0 {
if sel, ok := call.Args[0].(*ast.SelectorExpr); ok {
if x, ok := sel.X.(*ast.Ident); ok {
if x.Name == "os" && strings.HasPrefix(sel.Sel.Name, "Std") {
pass.ReportRangef(call, "%s does not take io.Writer but has first arg %s", name, astutil.Format(pass.Fset, call.Args[0]))
}
}
}
}
arg := args[0]
if s, ok := stringConstantExpr(pass, arg); ok {
// Ignore trailing % character
// The % in "abc 0.0%" couldn't be a formatting directive.
s = strings.TrimSuffix(s, "%")
if strings.Contains(s, "%") {
for _, m := range printFormatRE.FindAllString(s, -1) {
// Allow %XX where XX are hex digits,
// as this is common in URLs.
if len(m) >= 3 && isHex(m[1]) && isHex(m[2]) {
continue
}
pass.ReportRangef(call, "%s call has possible Printf formatting directive %s", name, m)
break // report only the first one
}
}
}
if strings.HasSuffix(name, "ln") {
// The last item, if a string, should not have a newline.
arg = args[len(args)-1]
if s, ok := stringConstantExpr(pass, arg); ok {
if strings.HasSuffix(s, "\n") {
pass.ReportRangef(call, "%s arg list ends with redundant newline", name)
}
}
}
for _, arg := range args {
if isFunctionValue(pass, arg) {
pass.ReportRangef(call, "%s arg %s is a func value, not called", name, astutil.Format(pass.Fset, arg))
}
if methodName, ok := recursiveStringer(pass, arg); ok {
pass.ReportRangef(call, "%s arg %s causes recursive call to %s method", name, astutil.Format(pass.Fset, arg), methodName)
}
}
}
// count(n, what) returns "1 what" or "N whats"
// (assuming the plural of what is whats).
func count(n int, what string) string {
if n == 1 {
return "1 " + what
}
return fmt.Sprintf("%d %ss", n, what)
}
// stringSet is a set-of-nonempty-strings-valued flag.
// Note: elements without a '.' get lower-cased.
type stringSet map[string]bool
func (ss stringSet) String() string {
var list []string
for name := range ss {
list = append(list, name)
}
sort.Strings(list)
return strings.Join(list, ",")
}
func (ss stringSet) Set(flag string) error {
for name := range strings.SplitSeq(flag, ",") {
if len(name) == 0 {
return fmt.Errorf("empty string")
}
if !strings.Contains(name, ".") {
name = strings.ToLower(name)
}
ss[name] = true
}
return nil
}
// isHex reports whether b is a hex digit.
func isHex(b byte) bool {
return '0' <= b && b <= '9' ||
'A' <= b && b <= 'F' ||
'a' <= b && b <= 'f'
}
================================================
FILE: go/analysis/passes/printf/printf_test.go
================================================
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package printf_test
import (
"path/filepath"
"testing"
"golang.org/x/tools/go/analysis/analysistest"
"golang.org/x/tools/go/analysis/passes/printf"
"golang.org/x/tools/internal/testenv"
"golang.org/x/tools/internal/testfiles"
)
func Test(t *testing.T) {
testdata := analysistest.TestData()
printf.Analyzer.Flags.Set("funcs", "Warn,Warnf")
analysistest.Run(t, testdata, printf.Analyzer,
"a", "b", "nofmt", "nonconst", "typeparams", "issue68744", "issue70572", "issue72850", "issue76616")
}
func TestNonConstantFmtString_Go123(t *testing.T) {
testenv.NeedsGo1Point(t, 23)
dir := testfiles.ExtractTxtarFileToTmp(t, filepath.Join(analysistest.TestData(), "nonconst_go123.txtar"))
analysistest.RunWithSuggestedFixes(t, dir, printf.Analyzer, "example.com/nonconst")
}
func TestNonConstantFmtString_Go124(t *testing.T) {
testenv.NeedsGo1Point(t, 24)
dir := testfiles.ExtractTxtarFileToTmp(t, filepath.Join(analysistest.TestData(), "nonconst_go124.txtar"))
analysistest.RunWithSuggestedFixes(t, dir, printf.Analyzer, "example.com/nonconst")
}
================================================
FILE: go/analysis/passes/printf/testdata/nonconst_go123.txtar
================================================
This test checks for the correct suppression (or activation) of the
non-constant format string check (golang/go#60529), in a go1.23 module.
See golang/go#71485 for details.
-- go.mod --
module example.com/nonconst
go 1.23
-- nonconst.go --
package nonconst
import (
"fmt"
"log"
"os"
)
func _(s string) {
fmt.Printf(s)
fmt.Printf(s, "arg")
fmt.Fprintf(os.Stderr, s)
log.Printf(s)
}
-- nonconst_go124.go --
//go:build go1.24
package nonconst
import (
"fmt"
"log"
"os"
)
// With Go 1.24, the analyzer should be activated, as this is a go1.24 file.
func _(s string) {
fmt.Printf(s) // want `non-constant format string in call to fmt.Printf`
fmt.Printf(s, "arg")
fmt.Fprintf(os.Stderr, s) // want `non-constant format string in call to fmt.Fprintf`
log.Printf(s) // want `non-constant format string in call to log.Printf`
}
-- nonconst_go124.go.golden --
//go:build go1.24
package nonconst
import (
"fmt"
"log"
"os"
)
// With Go 1.24, the analyzer should be activated, as this is a go1.24 file.
func _(s string) {
fmt.Printf("%s", s) // want `non-constant format string in call to fmt.Printf`
fmt.Printf(s, "arg")
fmt.Fprintf(os.Stderr, "%s", s) // want `non-constant format string in call to fmt.Fprintf`
log.Printf("%s", s) // want `non-constant format string in call to log.Printf`
}
================================================
FILE: go/analysis/passes/printf/testdata/nonconst_go124.txtar
================================================
This test checks for the correct suppression (or activation) of the
non-constant format string check (golang/go#60529), in a go1.24 module.
See golang/go#71485 for details.
-- go.mod --
module example.com/nonconst
go 1.24
-- nonconst.go --
package nonconst
import (
"fmt"
"log"
"os"
)
func _(s string) {
fmt.Printf(s) // want `non-constant format string in call to fmt.Printf`
fmt.Printf(s, "arg")
fmt.Fprintf(os.Stderr, s) // want `non-constant format string in call to fmt.Fprintf`
log.Printf(s) // want `non-constant format string in call to log.Printf`
}
-- nonconst.go.golden --
package nonconst
import (
"fmt"
"log"
"os"
)
func _(s string) {
fmt.Printf("%s", s) // want `non-constant format string in call to fmt.Printf`
fmt.Printf(s, "arg")
fmt.Fprintf(os.Stderr, "%s", s) // want `non-constant format string in call to fmt.Fprintf`
log.Printf("%s", s) // want `non-constant format string in call to log.Printf`
}
-- nonconst_go123.go --
//go:build go1.23
package nonconst
import (
"fmt"
"log"
"os"
)
// The analyzer should be silent, as this is a go1.23 file.
func _(s string) {
fmt.Printf(s)
fmt.Printf(s, "arg")
fmt.Fprintf(os.Stderr, s)
log.Printf(s)
}
================================================
FILE: go/analysis/passes/printf/testdata/src/a/a.go
================================================
// Copyright 2010 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// This file contains tests for the printf checker.
package a
import (
"fmt"
logpkg "log" // renamed to make it harder to see
"math"
"os"
"testing"
"unsafe" // just for test case printing unsafe.Pointer
// For testing printf-like functions from external package.
// "github.com/foobar/externalprintf"
"b"
)
func UnsafePointerPrintfTest() {
var up unsafe.Pointer
fmt.Printf("%p, %x %X", up, up, up)
}
// Error methods that do not satisfy the Error interface and should be checked.
type errorTest1 int
func (errorTest1) Error(...interface{}) string {
return "hi"
}
type errorTest2 int // Analogous to testing's *T type.
func (errorTest2) Error(...interface{}) {
}
type errorTest3 int
func (errorTest3) Error() { // No return value.
}
type errorTest4 int
func (errorTest4) Error() int { // Different return type.
return 3
}
type errorTest5 int
func (errorTest5) error() { // niladic; don't complain if no args (was bug)
}
type errorTestOK int
func (errorTestOK) Error() string { return "" }
// This function never executes, but it serves as a simple test for the program.
// Test with make test.
func PrintfTests() {
var b bool
var i int
var r rune
var s string
var x float64
var p *int
var imap map[int]int
var fslice []float64
var c complex64
var err error
// Some good format/argtypes
fmt.Printf("")
fmt.Printf("%b %b %b", 3, i, x)
fmt.Printf("%c %c %c %c", 3, i, 'x', r)
fmt.Printf("%d %d %d", 3, i, imap)
fmt.Printf("%e %e %e %e", 3e9, x, fslice, c)
fmt.Printf("%E %E %E %E", 3e9, x, fslice, c)
fmt.Printf("%f %f %f %f", 3e9, x, fslice, c)
fmt.Printf("%F %F %F %F", 3e9, x, fslice, c)
fmt.Printf("%g %g %g %g", 3e9, x, fslice, c)
fmt.Printf("%G %G %G %G", 3e9, x, fslice, c)
fmt.Printf("%b %b %b %b", 3e9, x, fslice, c)
fmt.Printf("%o %o", 3, i)
fmt.Printf("%O %O", 3, i)
fmt.Printf("%p", p)
fmt.Printf("%q %q %q", byte(i), 'x', r)
fmt.Printf("%s %s %s", "hi", s, []byte{65})
fmt.Printf("%t %t", true, b)
fmt.Printf("%T %T", 3, i)
fmt.Printf("%U %U", 3, i)
fmt.Printf("%v %v", 3, i)
fmt.Printf("%x %x %x %x %x %x %x", 3, i, "hi", s, x, c, fslice)
fmt.Printf("%X %X %X %X %X %X %X", 3, i, "hi", s, x, c, fslice)
fmt.Printf("%.*s %d %g", 3, "hi", 23, 2.3)
fmt.Printf("%s", &stringerv)
fmt.Printf("%v", &stringerv)
fmt.Printf("%T", &stringerv)
fmt.Printf("%s", &embeddedStringerv)
fmt.Printf("%v", &embeddedStringerv)
fmt.Printf("%T", &embeddedStringerv)
fmt.Printf("%v", notstringerv)
fmt.Printf("%T", notstringerv)
fmt.Printf("%q", stringerarrayv)
fmt.Printf("%v", stringerarrayv)
fmt.Printf("%s", stringerarrayv)
fmt.Printf("%v", notstringerarrayv)
fmt.Printf("%T", notstringerarrayv)
fmt.Printf("%d", new(fmt.Formatter))
fmt.Printf("%*%", 2) // Ridiculous but allowed.
fmt.Printf("%s", interface{}(nil)) // Nothing useful we can say.
fmt.Printf("%a", interface{}(new(BoolFormatter))) // Could be a fmt.Formatter.
fmt.Printf("%g", 1+2i)
fmt.Printf("%#e %#E %#f %#F %#g %#G", 1.2, 1.2, 1.2, 1.2, 1.2, 1.2) // OK since Go 1.9
// Some bad format/argTypes
fmt.Printf("%b", "hi") // want "fmt.Printf format %b has arg \x22hi\x22 of wrong type string"
fmt.Printf("%t", c) // want "fmt.Printf format %t has arg c of wrong type complex64"
fmt.Printf("%t", 1+2i) // want `fmt.Printf format %t has arg 1 \+ 2i of wrong type complex128`
fmt.Printf("%c", 2.3) // want "fmt.Printf format %c has arg 2.3 of wrong type float64"
fmt.Printf("%d", 2.3) // want "fmt.Printf format %d has arg 2.3 of wrong type float64"
fmt.Printf("%e", "hi") // want `fmt.Printf format %e has arg "hi" of wrong type string`
fmt.Printf("%E", true) // want "fmt.Printf format %E has arg true of wrong type bool"
fmt.Printf("%f", "hi") // want "fmt.Printf format %f has arg \x22hi\x22 of wrong type string"
fmt.Printf("%F", 'x') // want "fmt.Printf format %F has arg 'x' of wrong type rune"
fmt.Printf("%g", "hi") // want `fmt.Printf format %g has arg "hi" of wrong type string`
fmt.Printf("%g", imap) // want `fmt.Printf format %g has arg imap of wrong type map\[int\]int`
fmt.Printf("%G", i) // want "fmt.Printf format %G has arg i of wrong type int"
fmt.Printf("%o", x) // want "fmt.Printf format %o has arg x of wrong type float64"
fmt.Printf("%O", x) // want "fmt.Printf format %O has arg x of wrong type float64"
fmt.Printf("%p", nil) // want "fmt.Printf format %p has arg nil of wrong type untyped nil"
fmt.Printf("%p", 23) // want "fmt.Printf format %p has arg 23 of wrong type int"
fmt.Printf("%q", x) // want "fmt.Printf format %q has arg x of wrong type float64"
fmt.Printf("%s", b) // want "fmt.Printf format %s has arg b of wrong type bool"
fmt.Printf("%s", byte(65)) // want `fmt.Printf format %s has arg byte\(65\) of wrong type byte`
fmt.Printf("%t", 23) // want "fmt.Printf format %t has arg 23 of wrong type int"
fmt.Printf("%U", x) // want "fmt.Printf format %U has arg x of wrong type float64"
fmt.Printf("%x", nil) // want "fmt.Printf format %x has arg nil of wrong type untyped nil"
fmt.Printf("%s", stringerv) // want "fmt.Printf format %s has arg stringerv of wrong type a.ptrStringer"
fmt.Printf("%t", stringerv) // want "fmt.Printf format %t has arg stringerv of wrong type a.ptrStringer"
fmt.Printf("%s", embeddedStringerv) // want "fmt.Printf format %s has arg embeddedStringerv of wrong type a.embeddedStringer"
fmt.Printf("%t", embeddedStringerv) // want "fmt.Printf format %t has arg embeddedStringerv of wrong type a.embeddedStringer"
fmt.Printf("%q", notstringerv) // want "fmt.Printf format %q has arg notstringerv of wrong type a.notstringer"
fmt.Printf("%t", notstringerv) // want "fmt.Printf format %t has arg notstringerv of wrong type a.notstringer"
fmt.Printf("%t", stringerarrayv) // want "fmt.Printf format %t has arg stringerarrayv of wrong type a.stringerarray"
fmt.Printf("%t", notstringerarrayv) // want "fmt.Printf format %t has arg notstringerarrayv of wrong type a.notstringerarray"
fmt.Printf("%q", notstringerarrayv) // want "fmt.Printf format %q has arg notstringerarrayv of wrong type a.notstringerarray"
fmt.Printf("%d", BoolFormatter(true)) // want `fmt.Printf format %d has arg BoolFormatter\(true\) of wrong type a.BoolFormatter`
fmt.Printf("%z", FormatterVal(true)) // correct (the type is responsible for formatting)
fmt.Printf("%d", FormatterVal(true)) // correct (the type is responsible for formatting)
fmt.Printf("%s", nonemptyinterface) // correct (the type is responsible for formatting)
fmt.Printf("%.*s %d %6g", 3, "hi", 23, 'x') // want "fmt.Printf format %6g has arg 'x' of wrong type rune"
fmt.Println() // not an error
fmt.Println("%s", "hi") // want "fmt.Println call has possible Printf formatting directive %s"
fmt.Println("%v", "hi") // want "fmt.Println call has possible Printf formatting directive %v"
fmt.Println("%T", "hi") // want "fmt.Println call has possible Printf formatting directive %T"
fmt.Println("%s"+" there", "hi") // want "fmt.Println call has possible Printf formatting directive %s"
fmt.Println("http://foo.com?q%2Fabc") // no diagnostic: %XX is excepted
fmt.Println("http://foo.com?q%2Fabc-%s") // want"fmt.Println call has possible Printf formatting directive %s"
fmt.Println("0.0%") // correct (trailing % couldn't be a formatting directive)
fmt.Printf("%s", "hi", 3) // want "fmt.Printf call needs 1 arg but has 2 args"
_ = fmt.Sprintf("%"+("s"), "hi", 3) // want "fmt.Sprintf call needs 1 arg but has 2 args"
fmt.Printf("%s%%%d", "hi", 3) // correct
fmt.Printf("%08s", "woo") // correct
fmt.Printf("% 8s", "woo") // correct
fmt.Printf("%.*d", 3, 3) // correct
fmt.Printf("%.*d x", 3, 3, 3, 3) // want "fmt.Printf call needs 2 args but has 4 args"
fmt.Printf("%.*d x", "hi", 3) // want `fmt.Printf format %.*d uses non-int "hi" as argument of \*`
fmt.Printf("%.*d x", i, 3) // correct
fmt.Printf("%.*d x", s, 3) // want `fmt.Printf format %.\*d uses non-int s as argument of \*`
fmt.Printf("%*% x", 0.22) // want `fmt.Printf format %\*% uses non-int 0.22 as argument of \*`
fmt.Printf("%q %q", multi()...) // ok
fmt.Printf("%#q", `blah`) // ok
fmt.Printf("%#b", 3) // ok
fmt.Printf("%q", 3) // ok before go1.26 (#72850)
// printf("now is the time", "buddy") // no error "a.printf call has arguments but no formatting directives"
Printf("now is the time", "buddy") // want "a.Printf call has arguments but no formatting directives"
Printf("hi") // ok
const format = "%s %s\n"
Printf(format, "hi", "there")
Printf(format, "hi") // want "a.Printf format %s reads arg #2, but call has 1 arg$"
Printf("%s %d %.3v %q", "str", 4) // want "a.Printf format %.3v reads arg #3, but call has 2 args"
f := new(ptrStringer)
f.Warn(0, "%s", "hello", 3) // want `\(\*a.ptrStringer\).Warn call has possible Printf formatting directive %s`
f.Warnf(0, "%s", "hello", 3) // want `\(\*a.ptrStringer\).Warnf call needs 1 arg but has 2 args`
f.Warnf(0, "%r", "hello") // want `\(\*a.ptrStringer\).Warnf format %r has unknown verb r`
f.Warnf(0, "%#s", "hello") // want `\(\*a.ptrStringer\).Warnf format %#s has unrecognized flag #`
f.Warn2(0, "%s", "hello", 3) // want `\(\*a.ptrStringer\).Warn2 call has possible Printf formatting directive %s`
f.Warnf2(0, "%s", "hello", 3) // want `\(\*a.ptrStringer\).Warnf2 call needs 1 arg but has 2 args`
f.Warnf2(0, "%r", "hello") // want `\(\*a.ptrStringer\).Warnf2 format %r has unknown verb r`
f.Warnf2(0, "%#s", "hello") // want `\(\*a.ptrStringer\).Warnf2 format %#s has unrecognized flag #`
f.Wrap(0, "%s", "hello", 3) // want `\(\*a.ptrStringer\).Wrap call has possible Printf formatting directive %s`
f.Wrapf(0, "%s", "hello", 3) // want `\(\*a.ptrStringer\).Wrapf call needs 1 arg but has 2 args`
f.Wrapf(0, "%r", "hello") // want `\(\*a.ptrStringer\).Wrapf format %r has unknown verb r`
f.Wrapf(0, "%#s", "hello") // want `\(\*a.ptrStringer\).Wrapf format %#s has unrecognized flag #`
f.Wrap2(0, "%s", "hello", 3) // want `\(\*a.ptrStringer\).Wrap2 call has possible Printf formatting directive %s`
f.Wrapf2(0, "%s", "hello", 3) // want `\(\*a.ptrStringer\).Wrapf2 call needs 1 arg but has 2 args`
f.Wrapf2(0, "%r", "hello") // want `\(\*a.ptrStringer\).Wrapf2 format %r has unknown verb r`
f.Wrapf2(0, "%#s", "hello") // want `\(\*a.ptrStringer\).Wrapf2 format %#s has unrecognized flag #`
fmt.Printf("%#s", FormatterVal(true)) // correct (the type is responsible for formatting)
Printf("d%", 2) // want "a.Printf format % is missing verb at end of string"
Printf("%d", percentDV)
Printf("%d", &percentDV)
Printf("%d", notPercentDV) // want "a.Printf format %d has arg notPercentDV of wrong type a.notPercentDStruct"
Printf("%d", ¬PercentDV) // want `a.Printf format %d has arg ¬PercentDV of wrong type \*a.notPercentDStruct`
Printf("%p", ¬PercentDV) // Works regardless: we print it as a pointer.
Printf("%q", &percentDV) // want `a.Printf format %q has arg &percentDV of wrong type \*a.percentDStruct`
Printf("%s", percentSV)
Printf("%s", &percentSV)
// Good argument reorderings.
Printf("%[1]d", 3)
Printf("%[1]*d", 3, 1)
Printf("%[2]*[1]d", 1, 3)
Printf("%[2]*.[1]*[3]d", 2, 3, 4)
fmt.Fprintf(os.Stderr, "%[2]*.[1]*[3]d", 2, 3, 4) // Use Fprintf to make sure we count arguments correctly.
// Bad argument reorderings.
Printf("%[xd", 3) // want `a.Printf format %\[xd is missing closing \]`
Printf("%[x]d x", 3) // want `a.Printf format has invalid argument index \[x\]`
Printf("%[3]*s x", "hi", 2) // want `a.Printf format %\[3]\*s reads arg #3, but call has 2 args`
_ = fmt.Sprintf("%[3]d x", 2) // want `fmt.Sprintf format %\[3]d reads arg #3, but call has 1 arg`
Printf("%[2]*.[1]*[3]d x", 2, "hi", 4) // want `a.Printf format %\[2]\*\.\[1\]\*\[3\]d uses non-int \x22hi\x22 as argument of \*`
Printf("%[0]s x", "arg1") // want `a.Printf format has invalid argument index \[0\]`
Printf("%[0]d x", 1) // want `a.Printf format has invalid argument index \[0\]`
Printf("%[3]*.[2*[1]f", 1, 2, 3) // want `a.Printf format has invalid argument index \[2\*\[1\]`
// Something that satisfies the error interface.
var e error
fmt.Println(e.Error()) // ok
// Something that looks like an error interface but isn't, such as the (*T).Error method
// in the testing package.
var et1 *testing.T
et1.Error() // ok
et1.Error("hi") // ok
et1.Error("%d", 3) // want `\(\*testing.common\).Error call has possible Printf formatting directive %d`
et1.Errorf("%s", 1) // want `\(\*testing.common\).Errorf format %s has arg 1 of wrong type int`
var et3 errorTest3
et3.Error() // ok, not an error method.
var et4 errorTest4
et4.Error() // ok, not an error method.
var et5 errorTest5
et5.error() // ok, not an error method.
// Interfaces can be used with any verb.
var iface interface {
ToTheMadness() bool // Method ToTheMadness usually returns false
}
fmt.Printf("%f", iface) // ok: fmt treats interfaces as transparent and iface may well have a float concrete type
// Can't print a function.
Printf("%d", someFunction) // want "a.Printf format %d arg someFunction is a func value, not called"
Printf("%v", someFunction) // want "a.Printf format %v arg someFunction is a func value, not called"
Println(someFunction) // want "a.Println arg someFunction is a func value, not called"
Printf("%p", someFunction) // ok: maybe someone wants to see the pointer
Printf("%T", someFunction) // ok: maybe someone wants to see the type
// Bug: used to recur forever.
Printf("%p %x", recursiveStructV, recursiveStructV.next)
Printf("%p %x", recursiveStruct1V, recursiveStruct1V.next) // want `a.Printf format %x has arg recursiveStruct1V\.next of wrong type \*a\.RecursiveStruct2`
Printf("%p %x", recursiveSliceV, recursiveSliceV)
Printf("%p %x", recursiveMapV, recursiveMapV)
// Special handling for Log.
math.Log(3) // OK
var t *testing.T
t.Log("%d", 3) // want `\(\*testing.common\).Log call has possible Printf formatting directive %d`
t.Logf("%d", 3)
t.Logf("%d", "hi") // want `\(\*testing.common\).Logf format %d has arg "hi" of wrong type string`
Errorf(1, "%d", 3) // OK
Errorf(1, "%d", "hi") // want `a.Errorf format %d has arg "hi" of wrong type string`
// Multiple string arguments before variadic args
errorf("WARNING", "foobar") // OK
errorf("INFO", "s=%s, n=%d", "foo", 1) // OK
errorf("ERROR", "%d") // want "a.errorf format %d reads arg #1, but call has 0 args"
var tb testing.TB
tb.Errorf("%s", 1) // want `\(testing.TB\).Errorf format %s has arg 1 of wrong type int`
// Printf from external package
// externalprintf.Printf("%d", 42) // OK
// externalprintf.Printf("foobar") // OK
// level := 123
// externalprintf.Logf(level, "%d", 42) // OK
// externalprintf.Errorf(level, level, "foo %q bar", "foobar") // OK
// externalprintf.Logf(level, "%d") // no error "Logf format %d reads arg #1, but call has 0 args"
// var formatStr = "%s %s"
// externalprintf.Sprintf(formatStr, "a", "b") // OK
// externalprintf.Logf(level, formatStr, "a", "b") // OK
// user-defined Println-like functions
ss := &someStruct{}
ss.Log(someFunction, "foo") // OK
ss.Error(someFunction, someFunction) // OK
ss.Println() // OK
ss.Println(1.234, "foo") // OK
ss.Println(1, someFunction) // no error "Println arg someFunction is a func value, not called"
ss.log(someFunction) // OK
ss.log(someFunction, "bar", 1.33) // OK
ss.log(someFunction, someFunction) // no error "log arg someFunction is a func value, not called"
// indexed arguments
Printf("%d %[3]d %d %[2]d x", 1, 2, 3, 4) // OK
Printf("%d %[0]d %d %[2]d x", 1, 2, 3, 4) // want `a.Printf format has invalid argument index \[0\]`
Printf("%d %[3]d %d %[-2]d x", 1, 2, 3, 4) // want `a.Printf format has invalid argument index \[-2\]`
Printf("%d %[3]d %d %[2234234234234]d x", 1, 2, 3, 4) // want `a.Printf format has invalid argument index \[2234234234234\]`
Printf("%d %[3]d %-10d %[2]d x", 1, 2, 3) // want "a.Printf format %-10d reads arg #4, but call has 3 args"
Printf("%[1][3]d x", 1, 2) // want `a.Printf format %\[1\]\[ has unknown verb \[`
Printf("%[1]d x", 1, 2) // OK
Printf("%d %[3]d %d %[2]d x", 1, 2, 3, 4, 5) // OK
// wrote Println but meant Fprintln
Printf("%p\n", os.Stdout) // OK
Println(os.Stdout, "hello") // want "a.Println does not take io.Writer but has first arg os.Stdout"
Printf(someString(), "hello") // OK
// Printf wrappers in package log should be detected automatically
logpkg.Fatal("%d", 1) // want "log.Fatal call has possible Printf formatting directive %d"
logpkg.Fatalf("%d", "x") // want `log.Fatalf format %d has arg "x" of wrong type string`
logpkg.Fatalln("%d", 1) // want "log.Fatalln call has possible Printf formatting directive %d"
logpkg.Panic("%d", 1) // want "log.Panic call has possible Printf formatting directive %d"
logpkg.Panicf("%d", "x") // want `log.Panicf format %d has arg "x" of wrong type string`
logpkg.Panicln("%d", 1) // want "log.Panicln call has possible Printf formatting directive %d"
logpkg.Print("%d", 1) // want "log.Print call has possible Printf formatting directive %d"
logpkg.Printf("%d", "x") // want `log.Printf format %d has arg "x" of wrong type string`
logpkg.Println("%d", 1) // want "log.Println call has possible Printf formatting directive %d"
// Methods too.
var l *logpkg.Logger
l.Fatal("%d", 1) // want `\(\*log.Logger\).Fatal call has possible Printf formatting directive %d`
l.Fatalf("%d", "x") // want `\(\*log.Logger\).Fatalf format %d has arg "x" of wrong type string`
l.Fatalln("%d", 1) // want `\(\*log.Logger\).Fatalln call has possible Printf formatting directive %d`
l.Panic("%d", 1) // want `\(\*log.Logger\).Panic call has possible Printf formatting directive %d`
l.Panicf("%d", "x") // want `\(\*log.Logger\).Panicf format %d has arg "x" of wrong type string`
l.Panicln("%d", 1) // want `\(\*log.Logger\).Panicln call has possible Printf formatting directive %d`
l.Print("%d", 1) // want `\(\*log.Logger\).Print call has possible Printf formatting directive %d`
l.Printf("%d", "x") // want `\(\*log.Logger\).Printf format %d has arg "x" of wrong type string`
l.Println("%d", 1) // want `\(\*log.Logger\).Println call has possible Printf formatting directive %d`
// Issue 26486
dbg("", 1) // no error "call has arguments but no formatting directive"
// %w
var errSubset interface {
Error() string
A()
}
_ = fmt.Errorf("%w", err) // OK
_ = fmt.Errorf("%#w", err) // OK
_ = fmt.Errorf("%[2]w %[1]s", "x", err) // OK
_ = fmt.Errorf("%[2]w %[1]s", e, "x") // want `fmt.Errorf format %\[2\]w has arg "x" of wrong type string`
_ = fmt.Errorf("%w", "x") // want `fmt.Errorf format %w has arg "x" of wrong type string`
_ = fmt.Errorf("%w %w", err, err) // OK
_ = fmt.Errorf("%w", interface{}(nil)) // want `fmt.Errorf format %w has arg interface{}\(nil\) of wrong type interface{}`
_ = fmt.Errorf("%w", errorTestOK(0)) // concrete value implements error
_ = fmt.Errorf("%w", errSubset) // interface value implements error
fmt.Printf("%w", err) // want `fmt.Printf does not support error-wrapping directive %w`
var wt *testing.T
wt.Errorf("%w", err) // want `\(\*testing.common\).Errorf does not support error-wrapping directive %w`
wt.Errorf("%[1][3]d x", 1, 2) // want `\(\*testing.common\).Errorf format %\[1\]\[ has unknown verb \[`
wt.Errorf("%[1]d x", 1, 2) // OK
// Errorf is a printfWrapper, not an errorfWrapper.
Errorf(0, "%w", err) // want `a.Errorf does not support error-wrapping directive %w`
// %w should work on fmt.Errorf-based wrappers.
var es errorfStruct
var eis errorfIntStruct
var ess errorfStringStruct
es.Errorf("%w", err) // OK
eis.Errorf(0, "%w", err) // OK
ess.Errorf("ERROR", "%w", err) // OK
fmt.Appendf(nil, "%d", "123") // want `wrong type`
fmt.Append(nil, "%d", 123) // want `fmt.Append call has possible Printf formatting directive %d`
}
func someString() string { return "X" }
type someStruct struct{}
// Log is non-variadic user-define Println-like function.
// Calls to this func must be skipped when checking
// for Println-like arguments.
func (ss *someStruct) Log(f func(), s string) {}
// Error is variadic user-define Println-like function.
// Calls to this func mustn't be checked for Println-like arguments,
// since variadic arguments type isn't interface{}.
func (ss *someStruct) Error(args ...func()) {}
// Println is variadic user-defined Println-like function.
// Calls to this func must be checked for Println-like arguments.
func (ss *someStruct) Println(args ...interface{}) {}
// log is variadic user-defined Println-like function.
// Calls to this func must be checked for Println-like arguments.
func (ss *someStruct) log(f func(), args ...interface{}) {}
// A function we use as a function value; it has no other purpose.
func someFunction() {}
// Printf is used by the test so we must declare it.
func Printf(format string, args ...interface{}) { // want Printf:"printfWrapper"
fmt.Printf(format, args...)
}
// Println is used by the test so we must declare it.
func Println(args ...interface{}) { // want Println:"printWrapper"
fmt.Println(args...)
}
// printf is used by the test so we must declare it.
func printf(format string, args ...interface{}) { // want printf:"printfWrapper"
fmt.Printf(format, args...)
}
// Errorf is used by the test for a case in which the first parameter
// is not a format string.
func Errorf(i int, format string, args ...interface{}) { // want Errorf:"printfWrapper"
fmt.Sprintf(format, args...)
}
// errorf is used by the test for a case in which the function accepts multiple
// string parameters before variadic arguments
func errorf(level, format string, args ...interface{}) { // want errorf:"printfWrapper"
fmt.Sprintf(format, args...)
}
type errorfStruct struct{}
// Errorf is used to test %w works on errorf wrappers.
func (errorfStruct) Errorf(format string, args ...interface{}) { // want Errorf:"errorfWrapper"
_ = fmt.Errorf(format, args...)
}
type errorfStringStruct struct{}
// Errorf is used by the test for a case in which the function accepts multiple
// string parameters before variadic arguments
func (errorfStringStruct) Errorf(level, format string, args ...interface{}) { // want Errorf:"errorfWrapper"
_ = fmt.Errorf(format, args...)
}
type errorfIntStruct struct{}
// Errorf is used by the test for a case in which the first parameter
// is not a format string.
func (errorfIntStruct) Errorf(i int, format string, args ...interface{}) { // want Errorf:"errorfWrapper"
_ = fmt.Errorf(format, args...)
}
// multi is used by the test.
func multi() []interface{} {
panic("don't call - testing only")
}
type stringer int
func (stringer) String() string { return "string" }
type ptrStringer float64
var stringerv ptrStringer
func (*ptrStringer) String() string {
return "string"
}
func (p *ptrStringer) Warn2(x int, args ...interface{}) string { // want Warn2:"printWrapper"
return p.Warn(x, args...)
}
func (p *ptrStringer) Warnf2(x int, format string, args ...interface{}) string { // want Warnf2:"printfWrapper"
return p.Warnf(x, format, args...)
}
// During testing -printf.funcs flag matches Warn.
func (*ptrStringer) Warn(x int, args ...interface{}) string {
return "warn"
}
// During testing -printf.funcs flag matches Warnf.
func (*ptrStringer) Warnf(x int, format string, args ...interface{}) string {
return "warnf"
}
func (p *ptrStringer) Wrap2(x int, args ...interface{}) string { // want Wrap2:"printWrapper"
return p.Wrap(x, args...)
}
func (p *ptrStringer) Wrapf2(x int, format string, args ...interface{}) string { // want Wrapf2:"printfWrapper"
return p.Wrapf(x, format, args...)
}
func (*ptrStringer) Wrap(x int, args ...interface{}) string { // want Wrap:"printWrapper"
return fmt.Sprint(args...)
}
func (*ptrStringer) Wrapf(x int, format string, args ...interface{}) string { // want Wrapf:"printfWrapper"
return fmt.Sprintf(format, args...)
}
func (*ptrStringer) BadWrap(x int, args ...interface{}) string {
return fmt.Sprint(args) // want "missing ... in args forwarded to print-like function"
}
func (*ptrStringer) BadWrapf(x int, format string, args ...interface{}) string {
return fmt.Sprintf(format, args) // want "missing ... in args forwarded to printf-like function"
}
func (*ptrStringer) WrapfFalsePositive(x int, arg1 string, arg2 ...interface{}) string {
return fmt.Sprintf("%s %v", arg1, arg2)
}
type embeddedStringer struct {
foo string
ptrStringer
bar int
}
var embeddedStringerv embeddedStringer
type notstringer struct {
f float64
}
var notstringerv notstringer
type stringerarray [4]float64
func (stringerarray) String() string {
return "string"
}
var stringerarrayv stringerarray
type notstringerarray [4]float64
var notstringerarrayv notstringerarray
var nonemptyinterface = interface {
f()
}(nil)
// A data type we can print with "%d".
type percentDStruct struct {
a int
b []byte
c *float64
}
var percentDV percentDStruct
// A data type we cannot print correctly with "%d".
type notPercentDStruct struct {
a int
b []byte
c bool
}
var notPercentDV notPercentDStruct
// A data type we can print with "%s".
type percentSStruct struct {
a string
b []byte
C stringerarray
}
var percentSV percentSStruct
type recursiveStringer int
func (s recursiveStringer) String() string {
_ = fmt.Sprintf("%d", s)
_ = fmt.Sprintf("%#v", s)
_ = fmt.Sprintf("%v", s) // want `fmt.Sprintf format %v with arg s causes recursive \(a.recursiveStringer\).String method call`
_ = fmt.Sprintf("%v", &s) // want `fmt.Sprintf format %v with arg &s causes recursive \(a.recursiveStringer\).String method call`
_ = fmt.Sprintf("%#x", s) // want `fmt.Sprintf format %#x with arg s causes recursive \(a.recursiveStringer\).String method call`
_ = fmt.Sprintf("%#x", &s) // want `fmt.Sprintf format %#x with arg &s causes recursive \(a.recursiveStringer\).String method call`
_ = fmt.Sprintf("%#X", s) // want `fmt.Sprintf format %#X with arg s causes recursive \(a.recursiveStringer\).String method call`
_ = fmt.Sprintf("%#X", &s) // want `fmt.Sprintf format %#X with arg &s causes recursive \(a.recursiveStringer\).String method call`
_ = fmt.Sprintf("%#q", s) // want `fmt.Sprintf format %#q with arg s causes recursive \(a.recursiveStringer\).String method call`
_ = fmt.Sprintf("%#q", &s) // want `fmt.Sprintf format %#q with arg &s causes recursive \(a.recursiveStringer\).String method call`
_ = fmt.Sprintf("%T", s) // ok; does not recursively call String
return fmt.Sprintln(s) // want `fmt.Sprintln arg s causes recursive call to \(a.recursiveStringer\).String method`
}
type recursivePtrStringer int
func (p *recursivePtrStringer) String() string {
_ = fmt.Sprintf("%v", *p)
_ = fmt.Sprint(&p) // ok; prints address
return fmt.Sprintln(p) // want `fmt.Sprintln arg p causes recursive call to \(\*a.recursivePtrStringer\).String method`
}
type recursiveError int
func (s recursiveError) Error() string {
_ = fmt.Sprintf("%d", s)
_ = fmt.Sprintf("%#v", s)
_ = fmt.Sprintf("%v", s) // want `fmt.Sprintf format %v with arg s causes recursive \(a.recursiveError\).Error method call`
_ = fmt.Sprintf("%v", &s) // want `fmt.Sprintf format %v with arg &s causes recursive \(a.recursiveError\).Error method call`
_ = fmt.Sprintf("%#x", s) // want `fmt.Sprintf format %#x with arg s causes recursive \(a.recursiveError\).Error method call`
_ = fmt.Sprintf("%#x", &s) // want `fmt.Sprintf format %#x with arg &s causes recursive \(a.recursiveError\).Error method call`
_ = fmt.Sprintf("%#X", s) // want `fmt.Sprintf format %#X with arg s causes recursive \(a.recursiveError\).Error method call`
_ = fmt.Sprintf("%#X", &s) // want `fmt.Sprintf format %#X with arg &s causes recursive \(a.recursiveError\).Error method call`
_ = fmt.Sprintf("%#q", s) // want `fmt.Sprintf format %#q with arg s causes recursive \(a.recursiveError\).Error method call`
_ = fmt.Sprintf("%#q", &s) // want `fmt.Sprintf format %#q with arg &s causes recursive \(a.recursiveError\).Error method call`
_ = fmt.Sprintf("%T", s) // ok; does not recursively call Error
return fmt.Sprintln(s) // want `fmt.Sprintln arg s causes recursive call to \(a.recursiveError\).Error method`
}
type recursivePtrError int
func (p *recursivePtrError) Error() string {
_ = fmt.Sprintf("%v", *p)
_ = fmt.Sprint(&p) // ok; prints address
return fmt.Sprintln(p) // want `fmt.Sprintln arg p causes recursive call to \(\*a.recursivePtrError\).Error method`
}
type recursiveStringerAndError int
func (s recursiveStringerAndError) String() string {
_ = fmt.Sprintf("%d", s)
_ = fmt.Sprintf("%#v", s)
_ = fmt.Sprintf("%v", s) // want `fmt.Sprintf format %v with arg s causes recursive \(a.recursiveStringerAndError\).String method call`
_ = fmt.Sprintf("%v", &s) // want `fmt.Sprintf format %v with arg &s causes recursive \(a.recursiveStringerAndError\).String method call`
_ = fmt.Sprintf("%#x", s) // want `fmt.Sprintf format %#x with arg s causes recursive \(a.recursiveStringerAndError\).String method call`
_ = fmt.Sprintf("%#x", &s) // want `fmt.Sprintf format %#x with arg &s causes recursive \(a.recursiveStringerAndError\).String method call`
_ = fmt.Sprintf("%#X", s) // want `fmt.Sprintf format %#X with arg s causes recursive \(a.recursiveStringerAndError\).String method call`
_ = fmt.Sprintf("%#X", &s) // want `fmt.Sprintf format %#X with arg &s causes recursive \(a.recursiveStringerAndError\).String method call`
_ = fmt.Sprintf("%#q", s) // want `fmt.Sprintf format %#q with arg s causes recursive \(a.recursiveStringerAndError\).String method call`
_ = fmt.Sprintf("%#q", &s) // want `fmt.Sprintf format %#q with arg &s causes recursive \(a.recursiveStringerAndError\).String method call`
_ = fmt.Sprintf("%T", s) // ok; does not recursively call String
return fmt.Sprintln(s) // want `fmt.Sprintln arg s causes recursive call to \(a.recursiveStringerAndError\).String method`
}
func (s recursiveStringerAndError) Error() string {
_ = fmt.Sprintf("%d", s)
_ = fmt.Sprintf("%#v", s)
_ = fmt.Sprintf("%v", s) // want `fmt.Sprintf format %v with arg s causes recursive \(a.recursiveStringerAndError\).Error method call`
_ = fmt.Sprintf("%v", &s) // want `fmt.Sprintf format %v with arg &s causes recursive \(a.recursiveStringerAndError\).Error method call`
_ = fmt.Sprintf("%#x", s) // want `fmt.Sprintf format %#x with arg s causes recursive \(a.recursiveStringerAndError\).Error method call`
_ = fmt.Sprintf("%#x", &s) // want `fmt.Sprintf format %#x with arg &s causes recursive \(a.recursiveStringerAndError\).Error method call`
_ = fmt.Sprintf("%#X", s) // want `fmt.Sprintf format %#X with arg s causes recursive \(a.recursiveStringerAndError\).Error method call`
_ = fmt.Sprintf("%#X", &s) // want `fmt.Sprintf format %#X with arg &s causes recursive \(a.recursiveStringerAndError\).Error method call`
_ = fmt.Sprintf("%#q", s) // want `fmt.Sprintf format %#q with arg s causes recursive \(a.recursiveStringerAndError\).Error method call`
_ = fmt.Sprintf("%#q", &s) // want `fmt.Sprintf format %#q with arg &s causes recursive \(a.recursiveStringerAndError\).Error method call`
_ = fmt.Sprintf("%T", s) // ok; does not recursively call Error
return fmt.Sprintln(s) // want `fmt.Sprintln arg s causes recursive call to \(a.recursiveStringerAndError\).Error method`
}
type recursivePtrStringerAndError int
func (p *recursivePtrStringerAndError) String() string {
_ = fmt.Sprintf("%v", *p)
_ = fmt.Sprint(&p) // ok; prints address
return fmt.Sprintln(p) // want `fmt.Sprintln arg p causes recursive call to \(\*a.recursivePtrStringerAndError\).String method`
}
func (p *recursivePtrStringerAndError) Error() string {
_ = fmt.Sprintf("%v", *p)
_ = fmt.Sprint(&p) // ok; prints address
return fmt.Sprintln(p) // want `fmt.Sprintln arg p causes recursive call to \(\*a.recursivePtrStringerAndError\).Error method`
}
// implements a String() method but with non-matching return types
type nonStringerWrongReturn int
func (s nonStringerWrongReturn) String() (string, error) {
return "", fmt.Errorf("%v", s)
}
// implements a String() method but with non-matching arguments
type nonStringerWrongArgs int
func (s nonStringerWrongArgs) String(i int) string {
return fmt.Sprintf("%d%v", i, s)
}
type cons struct {
car int
cdr *cons
}
func (cons *cons) String() string {
if cons == nil {
return "nil"
}
_ = fmt.Sprint(cons.cdr) // don't want "recursive call" diagnostic
return fmt.Sprintf("(%d . %v)", cons.car, cons.cdr) // don't want "recursive call" diagnostic
}
type BoolFormatter bool
func (*BoolFormatter) Format(fmt.State, rune) {
}
// Formatter with value receiver
type FormatterVal bool
func (FormatterVal) Format(fmt.State, rune) {
}
type RecursiveSlice []RecursiveSlice
var recursiveSliceV = &RecursiveSlice{}
type RecursiveMap map[int]RecursiveMap
var recursiveMapV = make(RecursiveMap)
type RecursiveStruct struct {
next *RecursiveStruct
}
var recursiveStructV = &RecursiveStruct{}
type RecursiveStruct1 struct {
next *RecursiveStruct2
}
type RecursiveStruct2 struct {
next *RecursiveStruct1
}
var recursiveStruct1V = &RecursiveStruct1{}
type unexportedInterface struct {
f interface{}
}
// Issue 17798: unexported ptrStringer cannot be formatted.
type unexportedStringer struct {
t ptrStringer
}
type unexportedStringerOtherFields struct {
s string
t ptrStringer
S string
}
// Issue 17798: unexported error cannot be formatted.
type unexportedError struct {
e error
}
type unexportedErrorOtherFields struct {
s string
e error
S string
}
type errorer struct{}
func (e errorer) Error() string { return "errorer" }
type unexportedCustomError struct {
e errorer
}
type errorInterface interface {
error
ExtraMethod()
}
type unexportedErrorInterface struct {
e errorInterface
}
func UnexportedStringerOrError() {
fmt.Printf("%s", unexportedInterface{"foo"}) // ok; prints {foo}
fmt.Printf("%s", unexportedInterface{3}) // ok; we can't see the problem
us := unexportedStringer{}
fmt.Printf("%s", us) // want "Printf format %s has arg us of wrong type a.unexportedStringer"
fmt.Printf("%s", &us) // want "Printf format %s has arg &us of wrong type [*]a.unexportedStringer"
usf := unexportedStringerOtherFields{
s: "foo",
S: "bar",
}
fmt.Printf("%s", usf) // want "Printf format %s has arg usf of wrong type a.unexportedStringerOtherFields"
fmt.Printf("%s", &usf) // want "Printf format %s has arg &usf of wrong type [*]a.unexportedStringerOtherFields"
ue := unexportedError{
e: &errorer{},
}
fmt.Printf("%s", ue) // want "Printf format %s has arg ue of wrong type a.unexportedError"
fmt.Printf("%s", &ue) // want "Printf format %s has arg &ue of wrong type [*]a.unexportedError"
uef := unexportedErrorOtherFields{
s: "foo",
e: &errorer{},
S: "bar",
}
fmt.Printf("%s", uef) // want "Printf format %s has arg uef of wrong type a.unexportedErrorOtherFields"
fmt.Printf("%s", &uef) // want "Printf format %s has arg &uef of wrong type [*]a.unexportedErrorOtherFields"
uce := unexportedCustomError{
e: errorer{},
}
fmt.Printf("%s", uce) // want "Printf format %s has arg uce of wrong type a.unexportedCustomError"
uei := unexportedErrorInterface{}
fmt.Printf("%s", uei) // want "Printf format %s has arg uei of wrong type a.unexportedErrorInterface"
fmt.Println("foo\n", "bar") // not an error
fmt.Println("foo\n") // want "Println arg list ends with redundant newline"
fmt.Println("foo" + "\n") // want "Println arg list ends with redundant newline"
fmt.Println("foo\\n") // not an error
fmt.Println(`foo\n`) // not an error
intSlice := []int{3, 4}
fmt.Printf("%s", intSlice) // want `fmt.Printf format %s has arg intSlice of wrong type \[\]int`
nonStringerArray := [1]unexportedStringer{{}}
fmt.Printf("%s", nonStringerArray) // want `fmt.Printf format %s has arg nonStringerArray of wrong type \[1\]a.unexportedStringer`
fmt.Printf("%s", []stringer{3, 4}) // not an error
fmt.Printf("%s", [2]stringer{3, 4}) // not an error
}
// TODO: Disable complaint about '0' for Go 1.10. To be fixed properly in 1.11.
// See issues 23598 and 23605.
func DisableErrorForFlag0() {
fmt.Printf("%0t", true)
}
// Issue 26486.
func dbg(format string, args ...interface{}) {
if format == "" {
format = "%v"
}
fmt.Printf(format, args...)
}
func PointersToCompoundTypes() {
stringSlice := []string{"a", "b"}
fmt.Printf("%s", &stringSlice) // not an error
intSlice := []int{3, 4}
fmt.Printf("%s", &intSlice) // want `fmt.Printf format %s has arg &intSlice of wrong type \*\[\]int`
stringArray := [2]string{"a", "b"}
fmt.Printf("%s", &stringArray) // not an error
intArray := [2]int{3, 4}
fmt.Printf("%s", &intArray) // want `fmt.Printf format %s has arg &intArray of wrong type \*\[2\]int`
stringStruct := struct{ F string }{"foo"}
fmt.Printf("%s", &stringStruct) // not an error
intStruct := struct{ F int }{3}
fmt.Printf("%s", &intStruct) // want `fmt.Printf format %s has arg &intStruct of wrong type \*struct{F int}`
stringMap := map[string]string{"foo": "bar"}
fmt.Printf("%s", &stringMap) // not an error
intMap := map[int]int{3: 4}
fmt.Printf("%s", &intMap) // want `fmt.Printf format %s has arg &intMap of wrong type \*map\[int\]int`
type T2 struct {
X string
}
type T1 struct {
X *T2
}
fmt.Printf("%s\n", T1{&T2{"x"}}) // want `fmt.Printf format %s has arg T1{&T2{.x.}} of wrong type a\.T1`
}
// Printf wrappers from external package
func externalPackage() {
b.Wrapf("%s", 1) // want "Wrapf format %s has arg 1 of wrong type int"
b.Wrap("%s", 1) // want "Wrap call has possible Printf formatting directive %s"
b.NoWrap("%s", 1)
b.Wrapf2("%s", 1) // want "Wrapf2 format %s has arg 1 of wrong type int"
}
func PointerVerbs() {
// Use booleans, so that we don't just format the elements like in
// PointersToCompoundTypes. Bools can only be formatted with verbs like
// %t and %v, and none of the ones below.
ptr := new(bool)
slice := []bool{}
array := [3]bool{}
map_ := map[bool]bool{}
chan_ := make(chan bool)
func_ := func(bool) {}
// %p, %b, %d, %o, %O, %x, and %X all support pointers.
fmt.Printf("%p", ptr)
fmt.Printf("%b", ptr)
fmt.Printf("%d", ptr)
fmt.Printf("%o", ptr)
fmt.Printf("%O", ptr)
fmt.Printf("%x", ptr)
fmt.Printf("%X", ptr)
// %p, %b, %d, %o, %O, %x, and %X all support channels.
fmt.Printf("%p", chan_)
fmt.Printf("%b", chan_)
fmt.Printf("%d", chan_)
fmt.Printf("%o", chan_)
fmt.Printf("%O", chan_)
fmt.Printf("%x", chan_)
fmt.Printf("%X", chan_)
// %p is the only one that supports funcs.
fmt.Printf("%p", func_)
fmt.Printf("%b", func_) // want `fmt.Printf format %b arg func_ is a func value, not called`
fmt.Printf("%d", func_) // want `fmt.Printf format %d arg func_ is a func value, not called`
fmt.Printf("%o", func_) // want `fmt.Printf format %o arg func_ is a func value, not called`
fmt.Printf("%O", func_) // want `fmt.Printf format %O arg func_ is a func value, not called`
fmt.Printf("%x", func_) // want `fmt.Printf format %x arg func_ is a func value, not called`
fmt.Printf("%X", func_) // want `fmt.Printf format %X arg func_ is a func value, not called`
// %p is the only one that supports all slices, by printing the address
// of the 0th element.
fmt.Printf("%p", slice) // supported; address of 0th element
fmt.Printf("%b", slice) // want `fmt.Printf format %b has arg slice of wrong type \[\]bool`
fmt.Printf("%d", slice) // want `fmt.Printf format %d has arg slice of wrong type \[\]bool`
fmt.Printf("%o", slice) // want `fmt.Printf format %o has arg slice of wrong type \[\]bool`
fmt.Printf("%O", slice) // want `fmt.Printf format %O has arg slice of wrong type \[\]bool`
fmt.Printf("%x", slice) // want `fmt.Printf format %x has arg slice of wrong type \[\]bool`
fmt.Printf("%X", slice) // want `fmt.Printf format %X has arg slice of wrong type \[\]bool`
// None support arrays.
fmt.Printf("%p", array) // want `fmt.Printf format %p has arg array of wrong type \[3\]bool`
fmt.Printf("%b", array) // want `fmt.Printf format %b has arg array of wrong type \[3\]bool`
fmt.Printf("%d", array) // want `fmt.Printf format %d has arg array of wrong type \[3\]bool`
fmt.Printf("%o", array) // want `fmt.Printf format %o has arg array of wrong type \[3\]bool`
fmt.Printf("%O", array) // want `fmt.Printf format %O has arg array of wrong type \[3\]bool`
fmt.Printf("%x", array) // want `fmt.Printf format %x has arg array of wrong type \[3\]bool`
fmt.Printf("%X", array) // want `fmt.Printf format %X has arg array of wrong type \[3\]bool`
// %p is the only one that supports all maps.
fmt.Printf("%p", map_) // supported; address of 0th element
fmt.Printf("%b", map_) // want `fmt.Printf format %b has arg map_ of wrong type map\[bool\]bool`
fmt.Printf("%d", map_) // want `fmt.Printf format %d has arg map_ of wrong type map\[bool\]bool`
fmt.Printf("%o", map_) // want `fmt.Printf format %o has arg map_ of wrong type map\[bool\]bool`
fmt.Printf("%O", map_) // want `fmt.Printf format %O has arg map_ of wrong type map\[bool\]bool`
fmt.Printf("%x", map_) // want `fmt.Printf format %x has arg map_ of wrong type map\[bool\]bool`
fmt.Printf("%X", map_) // want `fmt.Printf format %X has arg map_ of wrong type map\[bool\]bool`
}
// Tests of calls to anonymous print{,f}-wrapper
// functions assigned to variables (local/package/field).
// Test a local assigned an anonymous function using :=.
func _() {
printf := func(format string, args ...any) { // want printf:"printfWrapper"
println(fmt.Sprintf(format, args...))
}
printf("%s", 123) // want `printf format %s has arg 123 of wrong type int`
printf = nil // this doesn't undo the variable's printf-wrapper status
printf = func(format string, args ...any) {} // nor does this
printf("%s", 123) // want `printf format %s has arg 123 of wrong type int`
}
// Test a global variable.
func _() {
globalPrintf("%s", 123) // want `globalPrintf format %s has arg 123 of wrong type int`
// This assignment causes calls to GlobalWrapf2 to
// be checked as a wrapper in this package only.
b.GlobalWrapf2 = func(format string, args ...any) {
println(fmt.Sprintf(format, args...))
}
b.GlobalWrapf("%s", 123) // want `GlobalWrapf format %s has arg 123 of wrong type int`
b.GlobalWrapf2("%s", 123) // want `GlobalWrapf2 format %s has arg 123 of wrong type int`
b.GlobalNonWrapf("%s", 123) // nope
}
var globalPrintf = func(format string, args ...any) { // want globalPrintf:"printfWrapper"
println(fmt.Sprintf(format, args...))
}
// Test a non-wrapper anonymous function with a plausible signature.
func _() {
notprintf := func(format string, args ...any) {}
notprintf("%s", 123)
}
// Test '=' assignment.
// Even calls through the var before it is
// assigned a printf-like value are checked.
func _() {
var printf func(bogus int, format string, args ...any) // want printf:"printfWrapper"
printf(0, "%s", 123) // want `printf format %s has arg 123 of wrong type int`
printf = func(bogus int, format string, args ...any) {
println(fmt.Sprintf(format, args...))
}
printf(0, "%s", 123) // want `printf format %s has arg 123 of wrong type int`
}
// Test of literal wrapper function assigned to local variable.
func _() {
var printf = func(format string, args ...any) { // want printf:"printfWrapper"
println(fmt.Sprintf(format, args...))
}
printf("%s", 123) // want `printf format %s has arg 123 of wrong type int`
}
// Test of literal wrapper function assigned to struct field.
func _() {
var local struct {
printf func(format string, args ...any) // want printf:"printfWrapper"
}
local.printf = func(format string, args ...any) {
println(fmt.Sprintf(format, args...))
}
local.printf("%s", 123) // want `printf format %s has arg 123 of wrong type int`
// This assignment causes calls to Struct.Wrapf to
// be checked as a wrapper in this package only.
b.Struct.Wrapf2 = func(format string, args ...any) {
println(fmt.Sprintf(format, args...))
}
b.Struct.Wrapf("%s", 123) // want `Wrapf format %s has arg 123 of wrong type int`
b.Struct.Wrapf2("%s", 123) // want `Wrapf2 format %s has arg 123 of wrong type int`
b.Struct.NonWrapf("%s", 123) // nope
// variant using generic struct type.
{
type S[T any] struct {
printf func(x T, format string, args ...any) // want printf:"printfWrapper"
}
new(S[bool]).printf = func(_ bool, format string, args ...any) {
println(fmt.Sprintf(format, args...))
}
new(S[int]).printf(0, "%s", 123) // want `printf format %s has arg 123 of wrong type int`
}
}
================================================
FILE: go/analysis/passes/printf/testdata/src/a/a2.go
================================================
//go:build go1.26
package a
// Test of induction through interface assignments. (Applies only to
// interface methods declared in files that use at least Go 1.26.)
import "fmt"
type myLogger int
func (myLogger) Logf(format string, args ...any) { // want Logf:"printfWrapper"
print(fmt.Sprintf(format, args...))
}
// Logger is assigned from myLogger.
type Logger interface {
Logf(format string, args ...any) // want Logf:"printfWrapper"
}
var _ Logger = myLogger(0) // establishes that Logger wraps myLogger
func _(log Logger) {
log.Logf("%s", 123) // want `\(a.Logger\).Logf format %s has arg 123 of wrong type int`
}
// Logger2 is not assigned from myLogger.
type Logger2 interface {
Logf(format string, args ...any)
}
func _(log Logger2) {
log.Logf("%s", 123) // nope
}
================================================
FILE: go/analysis/passes/printf/testdata/src/b/b.go
================================================
package b
import "fmt"
// Wrapf is a printf wrapper.
func Wrapf(format string, args ...interface{}) { // want Wrapf:"printfWrapper"
fmt.Sprintf(format, args...)
}
// Wrap is a print wrapper.
func Wrap(args ...interface{}) { // want Wrap:"printWrapper"
fmt.Sprint(args...)
}
// NoWrap is not a wrapper.
func NoWrap(format string, args ...interface{}) {
}
// Wrapf2 is another printf wrapper.
func Wrapf2(format string, args ...interface{}) string { // want Wrapf2:"printfWrapper"
// This statement serves as an assertion that this function is a
// printf wrapper and that calls to it should be checked
// accordingly, even though the delegation below is obscured by
// the "("+format+")" operations.
if false {
fmt.Sprintf(format, args...)
}
// Effectively a printf delegation,
// but the printf checker can't see it.
return fmt.Sprintf("("+format+")", args...)
}
var (
// GlobalWrapf is assigned a literal printf wrapper
// in this package, and thus has a fact.
GlobalWrapf func(format string, args ...interface{}) // want GlobalWrapf:"printfWrapper"
// GlobalWrapf2 is also assigned, but in another package (a),
// and thus has no fact. Nonetheless it is checked as a
// wrapper in that package.
GlobalWrapf2 func(format string, args ...interface{})
// GlobalNonWrapf is never assigned a wrapper.
GlobalNonWrapf func(format string, args ...interface{})
)
var Struct struct {
// These fields follow the same pattern as the Global* vars.
Wrapf func(format string, args ...interface{}) // want Wrapf:"printfWrapper"
Wrapf2 func(format string, args ...interface{})
NonWrapf func(format string, args ...interface{})
}
func init() {
GlobalWrapf = func(format string, args ...any) {
println(fmt.Sprintf(format, args...))
}
GlobalWrapf("%s", 123) // want "GlobalWrapf format %s has arg 123 of wrong type int"
GlobalWrapf2("%s", 123) // nope
GlobalNonWrapf("%s", 123) // nope
Struct.Wrapf = func(format string, args ...any) {
println(fmt.Sprintf(format, args...))
}
Struct.Wrapf("%s", 123) // want "Wrapf format %s has arg 123 of wrong type int"
Struct.Wrapf2("%s", 123) // nope
Struct.NonWrapf("%s", 123) // nope
}
================================================
FILE: go/analysis/passes/printf/testdata/src/issue68744/issue68744.go
================================================
package issue68744
import "fmt"
// The use of "any" here is crucial to exercise the bug.
// (None of our earlier tests covered this vital detail!)
func wrapf(format string, args ...any) { // want wrapf:"printfWrapper"
fmt.Printf(format, args...)
}
func _() {
wrapf("%s", 123) // want `issue68744.wrapf format %s has arg 123 of wrong type int`
}
================================================
FILE: go/analysis/passes/printf/testdata/src/issue70572/issue70572.go
================================================
package issue70572
// Regression test for failure to detect that a call to B[bool].Printf
// was printf-like, because of a missing call to types.Func.Origin.
import "fmt"
type A struct{}
func (v A) Printf(format string, values ...any) { // want Printf:"printfWrapper"
fmt.Printf(format, values...)
}
type B[T any] struct{}
func (v B[T]) Printf(format string, values ...any) { // want Printf:"printfWrapper"
fmt.Printf(format, values...)
}
func main() {
var a A
var b B[bool]
a.Printf("x", 1) // want "arguments but no formatting directives"
b.Printf("x", 1) // want "arguments but no formatting directives"
}
================================================
FILE: go/analysis/passes/printf/testdata/src/issue72850/a_go125.go
================================================
//go:build !go1.26
package a
import "fmt"
var (
_ = fmt.Sprintf("%q", byte(65)) // ok
_ = fmt.Sprintf("%q", rune(65)) // ok
_ = fmt.Sprintf("%q", 123) // ok: pre-1.26 code allows %q on any integer
)
================================================
FILE: go/analysis/passes/printf/testdata/src/issue72850/a_go126.go
================================================
//go:build go1.26
package a
import "fmt"
var (
_ = fmt.Sprintf("%q", byte(65)) // ok
_ = fmt.Sprintf("%q", rune(65)) // ok
_ = fmt.Sprintf("%q", 123) // want `fmt.Sprintf format %q has arg 123 of wrong type int`
)
================================================
FILE: go/analysis/passes/printf/testdata/src/issue76616/issue76616.go
================================================
package issue76616
func _() {
_ = func() {}
}
================================================
FILE: go/analysis/passes/printf/testdata/src/nofmt/nofmt.go
================================================
package b
import (
"math/big"
"testing"
)
func formatBigInt(t *testing.T) {
t.Logf("%d\n", big.NewInt(4))
}
================================================
FILE: go/analysis/passes/printf/testdata/src/nonconst/nonconst.go
================================================
// Copyright 2024 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// This file contains tests of the printf checker's handling of non-constant
// format strings (golang/go#60529).
package nonconst
import (
"fmt"
"log"
"os"
)
// As the language version is empty here, and the new check is gated on go1.24,
// diagnostics are suppressed here.
func nonConstantFormat(s string) {
fmt.Printf(s)
fmt.Printf(s, "arg")
fmt.Fprintf(os.Stderr, s)
log.Printf(s)
}
================================================
FILE: go/analysis/passes/printf/testdata/src/typeparams/diagnostics.go
================================================
// Copyright 2021 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package typeparams
import "fmt"
func TestBasicTypeParams[T interface{ ~int }, E error, F fmt.Formatter, S fmt.Stringer, A any](t T, e E, f F, s S, a A) {
fmt.Printf("%d", t)
fmt.Printf("%s", t) // want "wrong type.*contains ~int"
fmt.Printf("%v", t)
fmt.Printf("%d", e) // want "wrong type"
fmt.Printf("%s", e)
fmt.Errorf("%w", e)
fmt.Printf("%a", f)
fmt.Printf("%d", f)
fmt.Printf("%T", f.Format)
fmt.Printf("%p", f.Format)
fmt.Printf("%s", s)
fmt.Errorf("%w", s) // want "wrong type"
fmt.Printf("%d", a) // want "wrong type"
fmt.Printf("%s", a) // want "wrong type"
fmt.Printf("%v", a)
fmt.Printf("%T", a)
}
type Constraint interface {
~int
}
func TestNamedConstraints_Issue49597[T Constraint](t T) {
fmt.Printf("%d", t)
fmt.Printf("%s", t) // want "wrong type.*contains ~int"
}
func TestNestedTypeParams[T interface{ ~int }, S interface{ ~string }]() {
var x struct {
f int
t T
}
fmt.Printf("%d", x)
fmt.Printf("%s", x) // want "wrong type"
var y struct {
f string
t S
}
fmt.Printf("%d", y) // want "wrong type"
fmt.Printf("%s", y)
var m1 map[T]T
fmt.Printf("%d", m1)
fmt.Printf("%s", m1) // want "wrong type"
var m2 map[S]S
fmt.Printf("%d", m2) // want "wrong type"
fmt.Printf("%s", m2)
}
type R struct {
F []R
}
func TestRecursiveTypeDefinition() {
var r []R
fmt.Printf("%d", r) // No error: avoids infinite recursion.
}
func TestRecursiveTypeParams[T1 ~[]T2, T2 ~[]T1 | string, T3 ~struct{ F T3 }](t1 T1, t2 T2, t3 T3) {
// No error is reported on the following lines to avoid infinite recursion.
fmt.Printf("%s", t1)
fmt.Printf("%s", t2)
fmt.Printf("%s", t3)
}
func TestRecusivePointers[T1 ~*T2, T2 ~*T1](t1 T1, t2 T2) {
// No error: we can't determine if pointer rules apply.
fmt.Printf("%s", t1)
fmt.Printf("%s", t2)
}
func TestEmptyTypeSet[T interface {
int | string
float64
}](t T) {
fmt.Printf("%s", t) // No error: empty type set.
}
func TestPointerRules[T ~*[]int | *[2]int](t T) {
var slicePtr *[]int
var arrayPtr *[2]int
fmt.Printf("%d", slicePtr)
fmt.Printf("%d", arrayPtr)
fmt.Printf("%d", t)
}
func TestInterfacePromotion[E interface {
~int
Error() string
}, S interface {
float64
String() string
}](e E, s S) {
fmt.Printf("%d", e)
fmt.Printf("%s", e)
fmt.Errorf("%w", e)
fmt.Printf("%d", s) // want "wrong type.*contains float64"
fmt.Printf("%s", s)
fmt.Errorf("%w", s) // want "wrong type"
}
type myInt int
func TestTermReduction[T1 interface{ ~int | string }, T2 interface {
~int | string
myInt
}](t1 T1, t2 T2) {
fmt.Printf("%d", t1) // want "wrong type.*contains string"
fmt.Printf("%s", t1) // want "wrong type.*contains ~int"
fmt.Printf("%d", t2)
fmt.Printf("%s", t2) // want "wrong type.*contains typeparams.myInt"
}
type U[T any] struct{}
func (u U[T]) String() string {
fmt.Println(u) // want `fmt.Println arg u causes recursive call to \(typeparams.U\[T\]\).String method`
return ""
}
type S[T comparable] struct {
t T
}
func (s S[T]) String() T {
fmt.Println(s) // Not flagged. We currently do not consider String() T to implement fmt.Stringer (see #55928).
return s.t
}
func TestInstanceStringer() {
// Tests String method with nil Scope (#55350)
fmt.Println(&S[string]{})
fmt.Println(&U[string]{})
}
================================================
FILE: go/analysis/passes/printf/testdata/src/typeparams/wrappers.go
================================================
// Copyright 2021 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package typeparams
import "fmt"
type N[T any] int
func (N[P]) Wrapf(p P, format string, args ...interface{}) { // want Wrapf:"printfWrapper"
fmt.Printf(format, args...)
}
func (*N[P]) PtrWrapf(p P, format string, args ...interface{}) { // want PtrWrapf:"printfWrapper"
fmt.Printf(format, args...)
}
func Printf[P any](p P, format string, args ...interface{}) { // want Printf:"printfWrapper"
fmt.Printf(format, args...)
}
================================================
FILE: go/analysis/passes/printf/types.go
================================================
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package printf
import (
"fmt"
"go/ast"
"go/types"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/internal/typeparams"
)
var errorType = types.Universe.Lookup("error").Type().Underlying().(*types.Interface)
// matchArgType reports an error if printf verb t is not appropriate for
// operand arg.
//
// If arg is a type parameter, the verb t must be appropriate for every type in
// the type parameter type set.
func matchArgType(pass *analysis.Pass, t printfArgType, arg ast.Expr) (reason string, ok bool) {
// %v, %T accept any argument type.
if t == anyType {
return "", true
}
typ := pass.TypesInfo.Types[arg].Type
if typ == nil {
return "", true // probably a type check problem
}
m := &argMatcher{t: t, seen: make(map[types.Type]bool)}
ok = m.match(typ, true)
return m.reason, ok
}
// argMatcher recursively matches types against the printfArgType t.
//
// To short-circuit recursion, it keeps track of types that have already been
// matched (or are in the process of being matched) via the seen map. Recursion
// arises from the compound types {map,chan,slice} which may be printed with %d
// etc. if that is appropriate for their element types, as well as from type
// parameters, which are expanded to the constituents of their type set.
//
// The reason field may be set to report the cause of the mismatch.
type argMatcher struct {
t printfArgType
seen map[types.Type]bool
reason string
}
// match checks if typ matches m's printf arg type. If topLevel is true, typ is
// the actual type of the printf arg, for which special rules apply. As a
// special case, top level type parameters pass topLevel=true when checking for
// matches among the constituents of their type set, as type arguments will
// replace the type parameter at compile time.
func (m *argMatcher) match(typ types.Type, topLevel bool) bool {
// %w accepts only errors.
if m.t == argError {
return types.ConvertibleTo(typ, errorType)
}
// If the type implements fmt.Formatter, we have nothing to check.
if isFormatter(typ) {
return true
}
// If we can use a string, might arg (dynamically) implement the Stringer or Error interface?
if m.t&argString != 0 && isConvertibleToString(typ) {
return true
}
if typ, _ := types.Unalias(typ).(*types.TypeParam); typ != nil {
// Avoid infinite recursion through type parameters.
if m.seen[typ] {
return true
}
m.seen[typ] = true
terms, err := typeparams.StructuralTerms(typ)
if err != nil {
return true // invalid type (possibly an empty type set)
}
if len(terms) == 0 {
// No restrictions on the underlying of typ. Type parameters implementing
// error, fmt.Formatter, or fmt.Stringer were handled above, and %v and
// %T was handled in matchType. We're about to check restrictions the
// underlying; if the underlying type is unrestricted there must be an
// element of the type set that violates one of the arg type checks
// below, so we can safely return false here.
if m.t == anyType { // anyType must have already been handled.
panic("unexpected printfArgType")
}
return false
}
// Only report a reason if typ is the argument type, otherwise it won't
// make sense. Note that it is not sufficient to check if topLevel == here,
// as type parameters can have a type set consisting of other type
// parameters.
reportReason := len(m.seen) == 1
for _, term := range terms {
if !m.match(term.Type(), topLevel) {
if reportReason {
if term.Tilde() {
m.reason = fmt.Sprintf("contains ~%s", term.Type())
} else {
m.reason = fmt.Sprintf("contains %s", term.Type())
}
}
return false
}
}
return true
}
typ = typ.Underlying()
if m.seen[typ] {
// We've already considered typ, or are in the process of considering it.
// In case we've already considered typ, it must have been valid (else we
// would have stopped matching). In case we're in the process of
// considering it, we must avoid infinite recursion.
//
// There are some pathological cases where returning true here is
// incorrect, for example `type R struct { F []R }`, but these are
// acceptable false negatives.
return true
}
m.seen[typ] = true
switch typ := typ.(type) {
case *types.Signature:
return m.t == argPointer
case *types.Map:
if m.t == argPointer {
return true
}
// Recur: map[int]int matches %d.
return m.match(typ.Key(), false) && m.match(typ.Elem(), false)
case *types.Chan:
return m.t&argPointer != 0
case *types.Array:
// Same as slice.
if types.Identical(typ.Elem().Underlying(), types.Typ[types.Byte]) && m.t&argString != 0 {
return true // %s matches []byte
}
// Recur: []int matches %d.
return m.match(typ.Elem(), false)
case *types.Slice:
// Same as array.
if types.Identical(typ.Elem().Underlying(), types.Typ[types.Byte]) && m.t&argString != 0 {
return true // %s matches []byte
}
if m.t == argPointer {
return true // %p prints a slice's 0th element
}
// Recur: []int matches %d. But watch out for
// type T []T
// If the element is a pointer type (type T[]*T), it's handled fine by the Pointer case below.
return m.match(typ.Elem(), false)
case *types.Pointer:
// Ugly, but dealing with an edge case: a known pointer to an invalid type,
// probably something from a failed import.
if typ.Elem() == types.Typ[types.Invalid] {
return true // special case
}
// If it's actually a pointer with %p, it prints as one.
if m.t == argPointer {
return true
}
if typeparams.IsTypeParam(typ.Elem()) {
return true // We don't know whether the logic below applies. Give up.
}
under := typ.Elem().Underlying()
switch under.(type) {
case *types.Struct: // see below
case *types.Array: // see below
case *types.Slice: // see below
case *types.Map: // see below
default:
// Check whether the rest can print pointers.
return m.t&argPointer != 0
}
// If it's a top-level pointer to a struct, array, slice, type param, or
// map, that's equivalent in our analysis to whether we can
// print the type being pointed to. Pointers in nested levels
// are not supported to minimize fmt running into loops.
if !topLevel {
return false
}
return m.match(under, false)
case *types.Struct:
// report whether all the elements of the struct match the expected type. For
// instance, with "%d" all the elements must be printable with the "%d" format.
for typf := range typ.Fields() {
if !m.match(typf.Type(), false) {
return false
}
if m.t&argString != 0 && !typf.Exported() && isConvertibleToString(typf.Type()) {
// Issue #17798: unexported Stringer or error cannot be properly formatted.
return false
}
}
return true
case *types.Interface:
// There's little we can do.
// Whether any particular verb is valid depends on the argument.
// The user may have reasonable prior knowledge of the contents of the interface.
return true
case *types.Basic:
switch typ.Kind() {
case types.UntypedBool,
types.Bool:
return m.t&argBool != 0
case types.Byte:
return m.t&(argInt|argByte) != 0
case types.Rune, types.UntypedRune:
return m.t&(argInt|argRune) != 0
case types.UntypedInt,
types.Int,
types.Int8,
types.Int16,
// see case Rune for int32
types.Int64,
types.Uint,
// see case Byte for uint8
types.Uint16,
types.Uint32,
types.Uint64,
types.Uintptr:
return m.t&argInt != 0
case types.UntypedFloat,
types.Float32,
types.Float64:
return m.t&argFloat != 0
case types.UntypedComplex,
types.Complex64,
types.Complex128:
return m.t&argComplex != 0
case types.UntypedString,
types.String:
return m.t&argString != 0
case types.UnsafePointer:
return m.t&(argPointer|argInt) != 0
case types.UntypedNil:
return false
case types.Invalid:
return true // Probably a type check problem.
}
panic("unreachable")
}
return false
}
func isConvertibleToString(typ types.Type) bool {
if bt, ok := types.Unalias(typ).(*types.Basic); ok && bt.Kind() == types.UntypedNil {
// We explicitly don't want untyped nil, which is
// convertible to both of the interfaces below, as it
// would just panic anyway.
return false
}
if types.ConvertibleTo(typ, errorType) {
return true // via .Error()
}
// Does it implement fmt.Stringer?
if obj, _, _ := types.LookupFieldOrMethod(typ, false, nil, "String"); obj != nil {
if fn, ok := obj.(*types.Func); ok {
sig := fn.Type().(*types.Signature)
if sig.Params().Len() == 0 &&
sig.Results().Len() == 1 &&
sig.Results().At(0).Type() == types.Typ[types.String] {
return true
}
}
}
return false
}
================================================
FILE: go/analysis/passes/reflectvaluecompare/cmd/reflectvaluecompare/main.go
================================================
// Copyright 2025 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// The reflectvaluecompare command applies the reflectvaluecompare
// checker to the specified packages of Go source code.
//
// Run with:
//
// $ go run ./go/analysis/passes/reflectvaluecompare/cmd/reflectvaluecompare -- packages...
package main
import (
"golang.org/x/tools/go/analysis/passes/reflectvaluecompare"
"golang.org/x/tools/go/analysis/singlechecker"
)
func main() { singlechecker.Main(reflectvaluecompare.Analyzer) }
================================================
FILE: go/analysis/passes/reflectvaluecompare/doc.go
================================================
// Copyright 2023 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package reflectvaluecompare defines an Analyzer that checks for accidentally
// using == or reflect.DeepEqual to compare reflect.Value values.
// See issues 43993 and 18871.
//
// # Analyzer reflectvaluecompare
//
// reflectvaluecompare: check for comparing reflect.Value values with == or reflect.DeepEqual
//
// The reflectvaluecompare checker looks for expressions of the form:
//
// v1 == v2
// v1 != v2
// reflect.DeepEqual(v1, v2)
//
// where v1 or v2 are reflect.Values. Comparing reflect.Values directly
// is almost certainly not correct, as it compares the reflect package's
// internal representation, not the underlying value.
// Likely what is intended is:
//
// v1.Interface() == v2.Interface()
// v1.Interface() != v2.Interface()
// reflect.DeepEqual(v1.Interface(), v2.Interface())
package reflectvaluecompare
================================================
FILE: go/analysis/passes/reflectvaluecompare/reflectvaluecompare.go
================================================
// Copyright 2021 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package reflectvaluecompare
import (
_ "embed"
"go/ast"
"go/token"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/ast/inspector"
"golang.org/x/tools/go/types/typeutil"
"golang.org/x/tools/internal/analysis/analyzerutil"
"golang.org/x/tools/internal/typesinternal"
)
//go:embed doc.go
var doc string
var Analyzer = &analysis.Analyzer{
Name: "reflectvaluecompare",
Doc: analyzerutil.MustExtractDoc(doc, "reflectvaluecompare"),
URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/reflectvaluecompare",
Requires: []*analysis.Analyzer{inspect.Analyzer},
Run: run,
}
func run(pass *analysis.Pass) (any, error) {
inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
nodeFilter := []ast.Node{
(*ast.BinaryExpr)(nil),
(*ast.CallExpr)(nil),
}
inspect.Preorder(nodeFilter, func(n ast.Node) {
switch n := n.(type) {
case *ast.BinaryExpr:
if n.Op != token.EQL && n.Op != token.NEQ {
return
}
if isReflectValue(pass, n.X) || isReflectValue(pass, n.Y) {
if n.Op == token.EQL {
pass.ReportRangef(n, "avoid using == with reflect.Value")
} else {
pass.ReportRangef(n, "avoid using != with reflect.Value")
}
}
case *ast.CallExpr:
obj := typeutil.Callee(pass.TypesInfo, n)
if typesinternal.IsFunctionNamed(obj, "reflect", "DeepEqual") && (isReflectValue(pass, n.Args[0]) || isReflectValue(pass, n.Args[1])) {
pass.ReportRangef(n, "avoid using reflect.DeepEqual with reflect.Value")
}
}
})
return nil, nil
}
// isReflectValue reports whether the type of e is reflect.Value.
func isReflectValue(pass *analysis.Pass, e ast.Expr) bool {
tv, ok := pass.TypesInfo.Types[e]
if !ok { // no type info, something else is wrong
return false
}
// See if the type is reflect.Value
if !typesinternal.IsTypeNamed(tv.Type, "reflect", "Value") {
return false
}
if _, ok := e.(*ast.CompositeLit); ok {
// This is reflect.Value{}. Don't treat that as an error.
// Users should probably use x.IsValid() rather than x == reflect.Value{}, but the latter isn't wrong.
return false
}
return true
}
================================================
FILE: go/analysis/passes/reflectvaluecompare/reflectvaluecompare_test.go
================================================
// Copyright 2021 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package reflectvaluecompare_test
import (
"testing"
"golang.org/x/tools/go/analysis/analysistest"
"golang.org/x/tools/go/analysis/passes/reflectvaluecompare"
)
func TestReflectValueCompare(t *testing.T) {
testdata := analysistest.TestData()
analysistest.Run(t, testdata, reflectvaluecompare.Analyzer, "a")
}
================================================
FILE: go/analysis/passes/reflectvaluecompare/testdata/src/a/a.go
================================================
// Copyright 2021 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// This file contains tests for the reflectvaluecompare checker.
package a
import (
"reflect"
)
func f() {
var x, y reflect.Value
var a, b interface{}
_ = x == y // want `avoid using == with reflect.Value`
_ = x == a // want `avoid using == with reflect.Value`
_ = a == x // want `avoid using == with reflect.Value`
_ = a == b
// Comparing to reflect.Value{} is ok.
_ = a == reflect.Value{}
_ = reflect.Value{} == a
_ = reflect.Value{} == reflect.Value{}
}
func g() {
var x, y reflect.Value
var a, b interface{}
_ = x != y // want `avoid using != with reflect.Value`
_ = x != a // want `avoid using != with reflect.Value`
_ = a != x // want `avoid using != with reflect.Value`
_ = a != b
// Comparing to reflect.Value{} is ok.
_ = a != reflect.Value{}
_ = reflect.Value{} != a
_ = reflect.Value{} != reflect.Value{}
}
func h() {
var x, y reflect.Value
var a, b interface{}
reflect.DeepEqual(x, y) // want `avoid using reflect.DeepEqual with reflect.Value`
reflect.DeepEqual(x, a) // want `avoid using reflect.DeepEqual with reflect.Value`
reflect.DeepEqual(a, x) // want `avoid using reflect.DeepEqual with reflect.Value`
reflect.DeepEqual(a, b)
// Comparing to reflect.Value{} is ok.
reflect.DeepEqual(reflect.Value{}, a)
reflect.DeepEqual(a, reflect.Value{})
reflect.DeepEqual(reflect.Value{}, reflect.Value{})
}
================================================
FILE: go/analysis/passes/shadow/cmd/shadow/main.go
================================================
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// The shadow command runs the shadow analyzer.
package main
import (
"golang.org/x/tools/go/analysis/passes/shadow"
"golang.org/x/tools/go/analysis/singlechecker"
)
func main() { singlechecker.Main(shadow.Analyzer) }
================================================
FILE: go/analysis/passes/shadow/doc.go
================================================
// Copyright 2023 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package shadow defines an Analyzer that checks for shadowed variables.
//
// # Analyzer shadow
//
// shadow: check for possible unintended shadowing of variables
//
// This analyzer check for shadowed variables.
// A shadowed variable is a variable declared in an inner scope
// with the same name and type as a variable in an outer scope,
// and where the outer variable is mentioned after the inner one
// is declared.
//
// (This definition can be refined; the module generates too many
// false positives and is not yet enabled by default.)
//
// For example:
//
// func BadRead(f *os.File, buf []byte) error {
// var err error
// for {
// n, err := f.Read(buf) // shadows the function variable 'err'
// if err != nil {
// break // causes return of wrong value
// }
// foo(buf)
// }
// return err
// }
package shadow
================================================
FILE: go/analysis/passes/shadow/shadow.go
================================================
// Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package shadow
import (
_ "embed"
"go/ast"
"go/token"
"go/types"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/ast/inspector"
"golang.org/x/tools/internal/analysis/analyzerutil"
)
// NOTE: Experimental. Not part of the vet suite.
//go:embed doc.go
var doc string
var Analyzer = &analysis.Analyzer{
Name: "shadow",
Doc: analyzerutil.MustExtractDoc(doc, "shadow"),
URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/shadow",
Requires: []*analysis.Analyzer{inspect.Analyzer},
Run: run,
}
// flags
var strict = false
func init() {
Analyzer.Flags.BoolVar(&strict, "strict", strict, "whether to be strict about shadowing; can be noisy")
}
func run(pass *analysis.Pass) (any, error) {
inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
spans := make(map[types.Object]span)
for id, obj := range pass.TypesInfo.Defs {
// Ignore identifiers that don't denote objects
// (package names, symbolic variables such as t
// in t := x.(type) of type switch headers).
if obj != nil {
growSpan(spans, obj, id.Pos(), id.End())
}
}
for id, obj := range pass.TypesInfo.Uses {
growSpan(spans, obj, id.Pos(), id.End())
}
for node, obj := range pass.TypesInfo.Implicits {
// A type switch with a short variable declaration
// such as t := x.(type) doesn't declare the symbolic
// variable (t in the example) at the switch header;
// instead a new variable t (with specific type) is
// declared implicitly for each case. Such variables
// are found in the types.Info.Implicits (not Defs)
// map. Add them here, assuming they are declared at
// the type cases' colon ":".
if cc, ok := node.(*ast.CaseClause); ok {
growSpan(spans, obj, cc.Colon, cc.Colon)
}
}
nodeFilter := []ast.Node{
(*ast.AssignStmt)(nil),
(*ast.GenDecl)(nil),
}
inspect.Preorder(nodeFilter, func(n ast.Node) {
switch n := n.(type) {
case *ast.AssignStmt:
checkShadowAssignment(pass, spans, n)
case *ast.GenDecl:
checkShadowDecl(pass, spans, n)
}
})
return nil, nil
}
// A span stores the minimum range of byte positions in the file in which a
// given variable (types.Object) is mentioned. It is lexically defined: it spans
// from the beginning of its first mention to the end of its last mention.
// A variable is considered shadowed (if strict is off) only if the
// shadowing variable is declared within the span of the shadowed variable.
// In other words, if a variable is shadowed but not used after the shadowed
// variable is declared, it is inconsequential and not worth complaining about.
// This simple check dramatically reduces the nuisance rate for the shadowing
// check, at least until something cleverer comes along.
//
// One wrinkle: A "naked return" is a silent use of a variable that the Span
// will not capture, but the compilers catch naked returns of shadowed
// variables so we don't need to.
//
// Cases this gets wrong (TODO):
// - If a for loop's continuation statement mentions a variable redeclared in
// the block, we should complain about it but don't.
// - A variable declared inside a function literal can falsely be identified
// as shadowing a variable in the outer function.
type span struct {
min token.Pos
max token.Pos
}
// contains reports whether the position is inside the span.
func (s span) contains(pos token.Pos) bool {
return s.min <= pos && pos < s.max
}
// growSpan expands the span for the object to contain the source range [pos, end).
func growSpan(spans map[types.Object]span, obj types.Object, pos, end token.Pos) {
if strict {
return // No need
}
s, ok := spans[obj]
if ok {
if s.min > pos {
s.min = pos
}
if s.max < end {
s.max = end
}
} else {
s = span{pos, end}
}
spans[obj] = s
}
// checkShadowAssignment checks for shadowing in a short variable declaration.
func checkShadowAssignment(pass *analysis.Pass, spans map[types.Object]span, a *ast.AssignStmt) {
if a.Tok != token.DEFINE {
return
}
if idiomaticShortRedecl(pass, a) {
return
}
for _, expr := range a.Lhs {
ident, ok := expr.(*ast.Ident)
if !ok {
pass.ReportRangef(expr, "invalid AST: short variable declaration of non-identifier")
return
}
checkShadowing(pass, spans, ident)
}
}
// idiomaticShortRedecl reports whether this short declaration can be ignored for
// the purposes of shadowing, that is, that any redeclarations it contains are deliberate.
func idiomaticShortRedecl(pass *analysis.Pass, a *ast.AssignStmt) bool {
// Don't complain about deliberate redeclarations of the form
// i := i
// Such constructs are idiomatic in range loops to create a new variable
// for each iteration. Another example is
// switch n := n.(type)
if len(a.Rhs) != len(a.Lhs) {
return false
}
// We know it's an assignment, so the LHS must be all identifiers. (We check anyway.)
for i, expr := range a.Lhs {
lhs, ok := expr.(*ast.Ident)
if !ok {
pass.ReportRangef(expr, "invalid AST: short variable declaration of non-identifier")
return true // Don't do any more processing.
}
switch rhs := a.Rhs[i].(type) {
case *ast.Ident:
if lhs.Name != rhs.Name {
return false
}
case *ast.TypeAssertExpr:
if id, ok := rhs.X.(*ast.Ident); ok {
if lhs.Name != id.Name {
return false
}
}
default:
return false
}
}
return true
}
// idiomaticRedecl reports whether this declaration spec can be ignored for
// the purposes of shadowing, that is, that any redeclarations it contains are deliberate.
func idiomaticRedecl(d *ast.ValueSpec) bool {
// Don't complain about deliberate redeclarations of the form
// var i, j = i, j
// Don't ignore redeclarations of the form
// var i = 3
if len(d.Names) != len(d.Values) {
return false
}
for i, lhs := range d.Names {
rhs, ok := d.Values[i].(*ast.Ident)
if !ok || lhs.Name != rhs.Name {
return false
}
}
return true
}
// checkShadowDecl checks for shadowing in a general variable declaration.
func checkShadowDecl(pass *analysis.Pass, spans map[types.Object]span, d *ast.GenDecl) {
if d.Tok != token.VAR {
return
}
for _, spec := range d.Specs {
valueSpec, ok := spec.(*ast.ValueSpec)
if !ok {
pass.ReportRangef(spec, "invalid AST: var GenDecl not ValueSpec")
return
}
// Don't complain about deliberate redeclarations of the form
// var i = i
if idiomaticRedecl(valueSpec) {
return
}
for _, ident := range valueSpec.Names {
checkShadowing(pass, spans, ident)
}
}
}
// checkShadowing checks whether the identifier shadows an identifier in an outer scope.
func checkShadowing(pass *analysis.Pass, spans map[types.Object]span, ident *ast.Ident) {
if ident.Name == "_" {
// Can't shadow the blank identifier.
return
}
obj := pass.TypesInfo.Defs[ident]
if obj == nil {
return
}
// obj.Parent.Parent is the surrounding scope. If we can find another declaration
// starting from there, we have a shadowed identifier.
_, shadowed := obj.Parent().Parent().LookupParent(obj.Name(), obj.Pos())
if shadowed == nil {
return
}
// Don't complain if it's shadowing a universe-declared identifier; that's fine.
if shadowed.Parent() == types.Universe {
return
}
if strict {
// The shadowed identifier must appear before this one to be an instance of shadowing.
if shadowed.Pos() > ident.Pos() {
return
}
} else {
// Don't complain if the span of validity of the shadowed identifier doesn't include
// the shadowing identifier.
span, ok := spans[shadowed]
if !ok {
pass.ReportRangef(ident, "internal error: no range for %q", ident.Name)
return
}
if !span.contains(ident.Pos()) {
return
}
}
// Don't complain if the types differ: that implies the programmer really wants two different things.
if types.Identical(obj.Type(), shadowed.Type()) {
line := pass.Fset.Position(shadowed.Pos()).Line
pass.ReportRangef(ident, "declaration of %q shadows declaration at line %d", obj.Name(), line)
}
}
================================================
FILE: go/analysis/passes/shadow/shadow_test.go
================================================
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package shadow_test
import (
"testing"
"golang.org/x/tools/go/analysis/analysistest"
"golang.org/x/tools/go/analysis/passes/shadow"
)
func Test(t *testing.T) {
testdata := analysistest.TestData()
analysistest.Run(t, testdata, shadow.Analyzer, "a")
}
================================================
FILE: go/analysis/passes/shadow/testdata/src/a/a.go
================================================
// Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// This file contains tests for the shadowed variable checker.
// Some of these errors are caught by the compiler (shadowed return parameters for example)
// but are nonetheless useful tests.
package a
import "os"
func ShadowRead(f *os.File, buf []byte) (err error) {
var x int
if f != nil {
err := 3 // OK - different type.
_ = err
}
if f != nil {
_, err := f.Read(buf) // want "declaration of .err. shadows declaration at line 13"
if err != nil {
return err
}
i := 3 // OK
_ = i
}
if f != nil {
x := one() // want "declaration of .x. shadows declaration at line 14"
var _, err = f.Read(buf) // want "declaration of .err. shadows declaration at line 13"
if x == 1 && err != nil {
return err
}
}
for i := 0; i < 10; i++ {
i := i // OK: obviously intentional idiomatic redeclaration
go func() {
println(i)
}()
}
var shadowTemp interface{}
switch shadowTemp := shadowTemp.(type) { // OK: obviously intentional idiomatic redeclaration
case int:
println("OK")
_ = shadowTemp
}
if shadowTemp := shadowTemp; true { // OK: obviously intentional idiomatic redeclaration
var f *os.File // OK because f is not mentioned later in the function.
// The declaration of x is a shadow because x is mentioned below.
var x int // want "declaration of .x. shadows declaration at line 14"
_, _, _ = x, f, shadowTemp
}
// Use a couple of variables to trigger shadowing errors.
_, _ = err, x
return
}
func one() int {
return 1
}
// Must not complain with an internal error for the
// implicitly declared type switch variable v.
func issue26725(x interface{}) int {
switch v := x.(type) {
case int, int32:
if v, ok := x.(int); ok {
return v
}
case int64:
return int(v)
}
return 0
}
// Verify that implicitly declared variables from
// type switches are considered in shadowing analysis.
func shadowTypeSwitch(a interface{}) {
switch t := a.(type) {
case int:
{
t := 0 // want "declaration of .t. shadows declaration at line 78"
_ = t
}
_ = t
case uint:
{
t := uint(0) // OK because t is not mentioned later in this function
_ = t
}
}
}
func shadowBlock() {
var a int
{
var a = 3 // want "declaration of .a. shadows declaration at line 94"
_ = a
}
_ = a
}
================================================
FILE: go/analysis/passes/shift/dead.go
================================================
// Copyright 2017 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package shift
// Simplified dead code detector.
// Used for skipping shift checks on unreachable arch-specific code.
import (
"go/ast"
"go/constant"
"go/types"
)
// updateDead puts unreachable "if" and "case" nodes into dead.
func updateDead(info *types.Info, dead map[ast.Node]bool, node ast.Node) {
if dead[node] {
// The node is already marked as dead.
return
}
// setDead marks the node and all the children as dead.
setDead := func(n ast.Node) {
ast.Inspect(n, func(node ast.Node) bool {
if node != nil {
dead[node] = true
}
return true
})
}
switch stmt := node.(type) {
case *ast.IfStmt:
// "if" branch is dead if its condition evaluates
// to constant false.
v := info.Types[stmt.Cond].Value
if v == nil {
return
}
if !constant.BoolVal(v) {
setDead(stmt.Body)
return
}
if stmt.Else != nil {
setDead(stmt.Else)
}
case *ast.SwitchStmt:
// Case clause with empty switch tag is dead if it evaluates
// to constant false.
if stmt.Tag == nil {
BodyLoopBool:
for _, stmt := range stmt.Body.List {
cc := stmt.(*ast.CaseClause)
if cc.List == nil {
// Skip default case.
continue
}
for _, expr := range cc.List {
v := info.Types[expr].Value
if v == nil || v.Kind() != constant.Bool || constant.BoolVal(v) {
continue BodyLoopBool
}
}
setDead(cc)
}
return
}
// Case clause is dead if its constant value doesn't match
// the constant value from the switch tag.
// TODO: This handles integer comparisons only.
v := info.Types[stmt.Tag].Value
if v == nil || v.Kind() != constant.Int {
return
}
tagN, ok := constant.Uint64Val(v)
if !ok {
return
}
BodyLoopInt:
for _, x := range stmt.Body.List {
cc := x.(*ast.CaseClause)
if cc.List == nil {
// Skip default case.
continue
}
for _, expr := range cc.List {
v := info.Types[expr].Value
if v == nil {
continue BodyLoopInt
}
n, ok := constant.Uint64Val(v)
if !ok || tagN == n {
continue BodyLoopInt
}
}
setDead(cc)
}
}
}
================================================
FILE: go/analysis/passes/shift/shift.go
================================================
// Copyright 2014 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package shift defines an Analyzer that checks for shifts that exceed
// the width of an integer.
package shift
// TODO(adonovan): integrate with ctrflow (CFG-based) dead code analysis. May
// have impedance mismatch due to its (non-)treatment of constant
// expressions (such as runtime.GOARCH=="386").
import (
"go/ast"
"go/constant"
"go/token"
"go/types"
"math"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/ast/inspector"
"golang.org/x/tools/internal/astutil"
"golang.org/x/tools/internal/typeparams"
)
const Doc = "check for shifts that equal or exceed the width of the integer"
var Analyzer = &analysis.Analyzer{
Name: "shift",
Doc: Doc,
URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/shift",
Requires: []*analysis.Analyzer{inspect.Analyzer},
Run: run,
}
func run(pass *analysis.Pass) (any, error) {
inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
// Do a complete pass to compute dead nodes.
dead := make(map[ast.Node]bool)
nodeFilter := []ast.Node{
(*ast.IfStmt)(nil),
(*ast.SwitchStmt)(nil),
}
inspect.Preorder(nodeFilter, func(n ast.Node) {
// TODO(adonovan): move updateDead into this file.
updateDead(pass.TypesInfo, dead, n)
})
nodeFilter = []ast.Node{
(*ast.AssignStmt)(nil),
(*ast.BinaryExpr)(nil),
}
inspect.Preorder(nodeFilter, func(node ast.Node) {
if dead[node] {
// Skip shift checks on unreachable nodes.
return
}
switch node := node.(type) {
case *ast.BinaryExpr:
if node.Op == token.SHL || node.Op == token.SHR {
checkLongShift(pass, node, node.X, node.Y)
}
case *ast.AssignStmt:
if len(node.Lhs) != 1 || len(node.Rhs) != 1 {
return
}
if node.Tok == token.SHL_ASSIGN || node.Tok == token.SHR_ASSIGN {
checkLongShift(pass, node, node.Lhs[0], node.Rhs[0])
}
}
})
return nil, nil
}
// checkLongShift checks if shift or shift-assign operations shift by more than
// the length of the underlying variable.
func checkLongShift(pass *analysis.Pass, node ast.Node, x, y ast.Expr) {
if pass.TypesInfo.Types[x].Value != nil {
// Ignore shifts of constants.
// These are frequently used for bit-twiddling tricks
// like ^uint(0) >> 63 for 32/64 bit detection and compatibility.
return
}
v := pass.TypesInfo.Types[y].Value
if v == nil {
return
}
u := constant.ToInt(v) // either an Int or Unknown
amt, ok := constant.Int64Val(u)
if !ok {
return
}
t := pass.TypesInfo.Types[x].Type
if t == nil {
return
}
var structuralTypes []types.Type
switch t := types.Unalias(t).(type) {
case *types.TypeParam:
terms, err := typeparams.StructuralTerms(t)
if err != nil {
return // invalid type
}
for _, term := range terms {
structuralTypes = append(structuralTypes, term.Type())
}
default:
structuralTypes = append(structuralTypes, t)
}
sizes := make(map[int64]struct{})
for _, t := range structuralTypes {
size := 8 * pass.TypesSizes.Sizeof(t)
sizes[size] = struct{}{}
}
minSize := int64(math.MaxInt64)
for size := range sizes {
if size < minSize {
minSize = size
}
}
if amt >= minSize {
ident := astutil.Format(pass.Fset, x)
qualifier := ""
if len(sizes) > 1 {
qualifier = "may be "
}
pass.ReportRangef(node, "%s (%s%d bits) too small for shift of %d", ident, qualifier, minSize, amt)
}
}
================================================
FILE: go/analysis/passes/shift/shift_test.go
================================================
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package shift_test
import (
"testing"
"golang.org/x/tools/go/analysis/analysistest"
"golang.org/x/tools/go/analysis/passes/shift"
)
func Test(t *testing.T) {
testdata := analysistest.TestData()
analysistest.Run(t, testdata, shift.Analyzer, "a", "typeparams")
}
================================================
FILE: go/analysis/passes/shift/testdata/src/a/a.go
================================================
// Copyright 2014 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// This file contains tests for the suspicious shift checker.
package shift
import (
"unsafe"
)
func ShiftTest() {
var i8 int8
_ = i8 << 7
_ = (i8 + 1) << 8 // want ".i8 . 1. .8 bits. too small for shift of 8"
_ = i8 << (7 + 1) // want "i8 .8 bits. too small for shift of 8"
_ = i8 >> 8 // want "i8 .8 bits. too small for shift of 8"
i8 <<= 8 // want "i8 .8 bits. too small for shift of 8"
i8 >>= 8 // want "i8 .8 bits. too small for shift of 8"
var i16 int16
_ = i16 << 15
_ = i16 << 16 // want "i16 .16 bits. too small for shift of 16"
_ = i16 >> 16 // want "i16 .16 bits. too small for shift of 16"
i16 <<= 16 // want "i16 .16 bits. too small for shift of 16"
i16 >>= 16 // want "i16 .16 bits. too small for shift of 16"
var i32 int32
_ = i32 << 31
_ = i32 << 32 // want "i32 .32 bits. too small for shift of 32"
_ = i32 >> 32 // want "i32 .32 bits. too small for shift of 32"
i32 <<= 32 // want "i32 .32 bits. too small for shift of 32"
i32 >>= 32 // want "i32 .32 bits. too small for shift of 32"
var i64 int64
_ = i64 << 63
_ = i64 << 64 // want "i64 .64 bits. too small for shift of 64"
_ = i64 >> 64 // want "i64 .64 bits. too small for shift of 64"
i64 <<= 64 // want "i64 .64 bits. too small for shift of 64"
i64 >>= 64 // want "i64 .64 bits. too small for shift of 64"
var u8 uint8
_ = u8 << 7
_ = u8 << 8 // want "u8 .8 bits. too small for shift of 8"
_ = u8 >> 8 // want "u8 .8 bits. too small for shift of 8"
u8 <<= 8 // want "u8 .8 bits. too small for shift of 8"
u8 >>= 8 // want "u8 .8 bits. too small for shift of 8"
var u16 uint16
_ = u16 << 15
_ = u16 << 16 // want "u16 .16 bits. too small for shift of 16"
_ = u16 >> 16 // want "u16 .16 bits. too small for shift of 16"
u16 <<= 16 // want "u16 .16 bits. too small for shift of 16"
u16 >>= 16 // want "u16 .16 bits. too small for shift of 16"
var u32 uint32
_ = u32 << 31
_ = u32 << 32 // want "u32 .32 bits. too small for shift of 32"
_ = u32 >> 32 // want "u32 .32 bits. too small for shift of 32"
u32 <<= 32 // want "u32 .32 bits. too small for shift of 32"
u32 >>= 32 // want "u32 .32 bits. too small for shift of 32"
var u64 uint64
_ = u64 << 63
_ = u64 << 64 // want "u64 .64 bits. too small for shift of 64"
_ = u64 >> 64 // want "u64 .64 bits. too small for shift of 64"
u64 <<= 64 // want "u64 .64 bits. too small for shift of 64"
u64 >>= 64 // want "u64 .64 bits. too small for shift of 64"
_ = u64 << u64 // Non-constant shifts should succeed.
var i int
_ = i << 31
const in = 8 * unsafe.Sizeof(i)
_ = i << in // want "too small for shift"
_ = i >> in // want "too small for shift"
i <<= in // want "too small for shift"
i >>= in // want "too small for shift"
const ix = 8*unsafe.Sizeof(i) - 1
_ = i << ix
_ = i >> ix
i <<= ix
i >>= ix
var u uint
_ = u << 31
const un = 8 * unsafe.Sizeof(u)
_ = u << un // want "too small for shift"
_ = u >> un // want "too small for shift"
u <<= un // want "too small for shift"
u >>= un // want "too small for shift"
const ux = 8*unsafe.Sizeof(u) - 1
_ = u << ux
_ = u >> ux
u <<= ux
u >>= ux
var p uintptr
_ = p << 31
const pn = 8 * unsafe.Sizeof(p)
_ = p << pn // want "too small for shift"
_ = p >> pn // want "too small for shift"
p <<= pn // want "too small for shift"
p >>= pn // want "too small for shift"
const px = 8*unsafe.Sizeof(p) - 1
_ = p << px
_ = p >> px
p <<= px
p >>= px
const oneIf64Bit = ^uint(0) >> 63 // allow large shifts of constants; they are used for 32/64 bit compatibility tricks
var h uintptr
h = h<<8 | (h >> (8 * (unsafe.Sizeof(h) - 1)))
h <<= 8 * unsafe.Sizeof(h) // want "too small for shift"
h >>= 7 * unsafe.Alignof(h)
h >>= 8 * unsafe.Alignof(h) // want "too small for shift"
}
func ShiftDeadCode() {
var i int
const iBits = 8 * unsafe.Sizeof(i)
if iBits <= 32 {
if iBits == 16 {
_ = i >> 8
} else {
_ = i >> 16
}
} else {
_ = i >> 32
}
if iBits >= 64 {
_ = i << 32
if iBits == 128 {
_ = i << 64
}
} else {
_ = i << 16
}
if iBits == 64 {
_ = i << 32
}
switch iBits {
case 128, 64:
_ = i << 32
default:
_ = i << 16
}
switch {
case iBits < 32:
_ = i << 16
case iBits > 64:
_ = i << 64
default:
_ = i << 64 // want "too small for shift"
}
}
func issue65939() {
a := 1
println(a << 2.0)
}
================================================
FILE: go/analysis/passes/shift/testdata/src/typeparams/typeparams.go
================================================
// Copyright 2021 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package typeparams
import "unsafe"
func GenericShiftTest[DifferentSize ~int8|int16|int64, SameSize int8|byte]() {
var d DifferentSize
_ = d << 7
_ = d << 8 // want "d .may be 8 bits. too small for shift of 8"
_ = d << 15 // want "d .may be 8 bits. too small for shift of 15"
_ = (d + 1) << 8 // want ".d . 1. .may be 8 bits. too small for shift of 8"
_ = (d + 1) << 16 // want ".d . 1. .may be 8 bits. too small for shift of 16"
_ = d << (7 + 1) // want "d .may be 8 bits. too small for shift of 8"
_ = d >> 8 // want "d .may be 8 bits. too small for shift of 8"
d <<= 8 // want "d .may be 8 bits. too small for shift of 8"
d >>= 8 // want "d .may be 8 bits. too small for shift of 8"
// go/types does not compute constant sizes for type parameters, so we do not
// report a diagnostic here.
_ = d << (8 * DifferentSize(unsafe.Sizeof(d)))
var s SameSize
_ = s << 7
_ = s << 8 // want "s .8 bits. too small for shift of 8"
_ = s << (7 + 1) // want "s .8 bits. too small for shift of 8"
_ = s >> 8 // want "s .8 bits. too small for shift of 8"
s <<= 8 // want "s .8 bits. too small for shift of 8"
s >>= 8 // want "s .8 bits. too small for shift of 8"
}
================================================
FILE: go/analysis/passes/sigchanyzer/doc.go
================================================
// Copyright 2023 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package sigchanyzer defines an Analyzer that detects
// misuse of unbuffered signal as argument to signal.Notify.
//
// # Analyzer sigchanyzer
//
// sigchanyzer: check for unbuffered channel of os.Signal
//
// This checker reports call expression of the form
//
// signal.Notify(c <-chan os.Signal, sig ...os.Signal),
//
// where c is an unbuffered channel, which can be at risk of missing the signal.
package sigchanyzer
================================================
FILE: go/analysis/passes/sigchanyzer/sigchanyzer.go
================================================
// Copyright 2020 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package sigchanyzer defines an Analyzer that detects
// misuse of unbuffered signal as argument to signal.Notify.
package sigchanyzer
import (
"bytes"
"slices"
_ "embed"
"go/ast"
"go/format"
"go/token"
"go/types"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/ast/inspector"
"golang.org/x/tools/internal/analysis/analyzerutil"
"golang.org/x/tools/internal/typesinternal"
)
//go:embed doc.go
var doc string
// Analyzer describes sigchanyzer analysis function detector.
var Analyzer = &analysis.Analyzer{
Name: "sigchanyzer",
Doc: analyzerutil.MustExtractDoc(doc, "sigchanyzer"),
URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/sigchanyzer",
Requires: []*analysis.Analyzer{inspect.Analyzer},
Run: run,
}
func run(pass *analysis.Pass) (any, error) {
if !typesinternal.Imports(pass.Pkg, "os/signal") {
return nil, nil // doesn't directly import signal
}
inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
nodeFilter := []ast.Node{
(*ast.CallExpr)(nil),
}
inspect.Preorder(nodeFilter, func(n ast.Node) {
call := n.(*ast.CallExpr)
if !isSignalNotify(pass.TypesInfo, call) {
return
}
var chanDecl *ast.CallExpr
switch arg := call.Args[0].(type) {
case *ast.Ident:
if decl, ok := findDecl(arg).(*ast.CallExpr); ok {
chanDecl = decl
}
case *ast.CallExpr:
// Only signal.Notify(make(chan os.Signal), os.Interrupt) is safe,
// conservatively treat others as not safe, see golang/go#45043
if isBuiltinMake(pass.TypesInfo, arg) {
return
}
chanDecl = arg
}
if chanDecl == nil || len(chanDecl.Args) != 1 {
return
}
// Make a copy of the channel's declaration to avoid
// mutating the AST. See https://golang.org/issue/46129.
chanDeclCopy := &ast.CallExpr{}
*chanDeclCopy = *chanDecl
chanDeclCopy.Args = slices.Clone(chanDecl.Args)
chanDeclCopy.Args = append(chanDeclCopy.Args, &ast.BasicLit{
Kind: token.INT,
Value: "1",
})
var buf bytes.Buffer
if err := format.Node(&buf, token.NewFileSet(), chanDeclCopy); err != nil {
return
}
pass.Report(analysis.Diagnostic{
Pos: call.Pos(),
End: call.End(),
Message: "misuse of unbuffered os.Signal channel as argument to signal.Notify",
SuggestedFixes: []analysis.SuggestedFix{{
Message: "Change to buffer channel",
TextEdits: []analysis.TextEdit{{
Pos: chanDecl.Pos(),
End: chanDecl.End(),
NewText: buf.Bytes(),
}},
}},
})
})
return nil, nil
}
func isSignalNotify(info *types.Info, call *ast.CallExpr) bool {
check := func(id *ast.Ident) bool {
obj := info.ObjectOf(id)
return obj.Name() == "Notify" && obj.Pkg().Path() == "os/signal"
}
switch fun := call.Fun.(type) {
case *ast.SelectorExpr:
return check(fun.Sel)
case *ast.Ident:
if fun, ok := findDecl(fun).(*ast.SelectorExpr); ok {
return check(fun.Sel)
}
return false
default:
return false
}
}
func findDecl(arg *ast.Ident) ast.Node {
if arg.Obj == nil {
return nil
}
switch as := arg.Obj.Decl.(type) {
case *ast.AssignStmt:
if len(as.Lhs) != len(as.Rhs) {
return nil
}
for i, lhs := range as.Lhs {
lid, ok := lhs.(*ast.Ident)
if !ok {
continue
}
if lid.Obj == arg.Obj {
return as.Rhs[i]
}
}
case *ast.ValueSpec:
if len(as.Names) != len(as.Values) {
return nil
}
for i, name := range as.Names {
if name.Obj == arg.Obj {
return as.Values[i]
}
}
}
return nil
}
func isBuiltinMake(info *types.Info, call *ast.CallExpr) bool {
typVal := info.Types[call.Fun]
if !typVal.IsBuiltin() {
return false
}
switch fun := call.Fun.(type) {
case *ast.Ident:
return info.ObjectOf(fun).Name() == "make"
default:
return false
}
}
================================================
FILE: go/analysis/passes/sigchanyzer/sigchanyzer_test.go
================================================
// Copyright 2020 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package sigchanyzer_test
import (
"testing"
"golang.org/x/tools/go/analysis/analysistest"
"golang.org/x/tools/go/analysis/passes/sigchanyzer"
)
func Test(t *testing.T) {
testdata := analysistest.TestData()
analysistest.RunWithSuggestedFixes(t, testdata, sigchanyzer.Analyzer, "a")
}
================================================
FILE: go/analysis/passes/sigchanyzer/testdata/src/a/a.go
================================================
package p
import (
"os"
ao "os"
"os/signal"
)
var c = make(chan os.Signal)
var d = make(chan os.Signal)
func f() {
c := make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt) // ok
_ = <-c
}
func g() {
c := make(chan os.Signal)
signal.Notify(c, os.Interrupt) // want "misuse of unbuffered os.Signal channel as argument to signal.Notify"
_ = <-c
}
func h() {
c := make(chan ao.Signal)
signal.Notify(c, os.Interrupt) // want "misuse of unbuffered os.Signal channel as argument to signal.Notify"
_ = <-c
}
func i() {
signal.Notify(d, os.Interrupt) // want "misuse of unbuffered os.Signal channel as argument to signal.Notify"
}
func j() {
c := make(chan os.Signal)
f := signal.Notify
f(c, os.Interrupt) // want "misuse of unbuffered os.Signal channel as argument to signal.Notify"
}
func k() {
signal.Notify(make(chan os.Signal), os.Interrupt) // ok
}
func l() {
signal.Notify(make(chan os.Signal, 1), os.Interrupt) // ok
}
func m() {
signal.Notify(make(chan ao.Signal, 1), os.Interrupt) // ok
}
func n() {
signal.Notify(make(chan ao.Signal), os.Interrupt) // ok
}
================================================
FILE: go/analysis/passes/sigchanyzer/testdata/src/a/a.go.golden
================================================
package p
import (
"os"
ao "os"
"os/signal"
)
var c = make(chan os.Signal)
var d = make(chan os.Signal, 1)
func f() {
c := make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt) // ok
_ = <-c
}
func g() {
c := make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt) // want "misuse of unbuffered os.Signal channel as argument to signal.Notify"
_ = <-c
}
func h() {
c := make(chan ao.Signal, 1)
signal.Notify(c, os.Interrupt) // want "misuse of unbuffered os.Signal channel as argument to signal.Notify"
_ = <-c
}
func i() {
signal.Notify(d, os.Interrupt) // want "misuse of unbuffered os.Signal channel as argument to signal.Notify"
}
func j() {
c := make(chan os.Signal, 1)
f := signal.Notify
f(c, os.Interrupt) // want "misuse of unbuffered os.Signal channel as argument to signal.Notify"
}
func k() {
signal.Notify(make(chan os.Signal), os.Interrupt) // ok
}
func l() {
signal.Notify(make(chan os.Signal, 1), os.Interrupt) // ok
}
func m() {
signal.Notify(make(chan ao.Signal, 1), os.Interrupt) // ok
}
func n() {
signal.Notify(make(chan ao.Signal), os.Interrupt) // ok
}
================================================
FILE: go/analysis/passes/slog/doc.go
================================================
// Copyright 2023 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package slog defines an Analyzer that checks for
// mismatched key-value pairs in log/slog calls.
//
// # Analyzer slog
//
// slog: check for invalid structured logging calls
//
// The slog checker looks for calls to functions from the log/slog
// package that take alternating key-value pairs. It reports calls
// where an argument in a key position is neither a string nor a
// slog.Attr, and where a final key is missing its value.
// For example,it would report
//
// slog.Warn("message", 11, "k") // slog.Warn arg "11" should be a string or a slog.Attr
//
// and
//
// slog.Info("message", "k1", v1, "k2") // call to slog.Info missing a final value
package slog
================================================
FILE: go/analysis/passes/slog/slog.go
================================================
// Copyright 2023 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// TODO(jba) deduce which functions wrap the log/slog functions, and use the
// fact mechanism to propagate this information, so we can provide diagnostics
// for user-supplied wrappers.
package slog
import (
_ "embed"
"fmt"
"go/ast"
"go/token"
"go/types"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/ast/inspector"
"golang.org/x/tools/go/types/typeutil"
"golang.org/x/tools/internal/analysis/analyzerutil"
"golang.org/x/tools/internal/astutil"
"golang.org/x/tools/internal/typesinternal"
)
//go:embed doc.go
var doc string
var Analyzer = &analysis.Analyzer{
Name: "slog",
Doc: analyzerutil.MustExtractDoc(doc, "slog"),
URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/slog",
Requires: []*analysis.Analyzer{inspect.Analyzer},
Run: run,
}
var stringType = types.Universe.Lookup("string").Type()
// A position describes what is expected to appear in an argument position.
type position int
const (
// key is an argument position that should hold a string key or an Attr.
key position = iota
// value is an argument position that should hold a value.
value
// unknown represents that we do not know if position should hold a key or a value.
unknown
)
func run(pass *analysis.Pass) (any, error) {
var attrType types.Type // The type of slog.Attr
inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
nodeFilter := []ast.Node{
(*ast.CallExpr)(nil),
}
inspect.Preorder(nodeFilter, func(node ast.Node) {
call := node.(*ast.CallExpr)
fn := typeutil.StaticCallee(pass.TypesInfo, call)
if fn == nil {
return // not a static call
}
if call.Ellipsis != token.NoPos {
return // skip calls with "..." args
}
skipArgs, ok := kvFuncSkipArgs(fn)
if !ok {
// Not a slog function that takes key-value pairs.
return
}
// Here we know that fn.Pkg() is "log/slog".
if attrType == nil {
attrType = fn.Pkg().Scope().Lookup("Attr").Type()
}
if isMethodExpr(pass.TypesInfo, call) {
// Call is to a method value. Skip the first argument.
skipArgs++
}
if len(call.Args) <= skipArgs {
// Too few args; perhaps there are no k-v pairs.
return
}
// Check this call.
// The first position should hold a key or Attr.
pos := key
var unknownArg ast.Expr // nil or the last unknown argument
for _, arg := range call.Args[skipArgs:] {
t := pass.TypesInfo.Types[arg].Type
switch pos {
case key:
// Expect a string or Attr.
switch {
case t == stringType:
pos = value
case isAttr(t):
pos = key
case types.IsInterface(t):
// As we do not do dataflow, we do not know what the dynamic type is.
// But we might be able to learn enough to make a decision.
if types.AssignableTo(stringType, t) {
// t must be an empty interface. So it can also be an Attr.
// We don't know enough to make an assumption.
pos = unknown
continue
} else if attrType != nil && types.AssignableTo(attrType, t) {
// Assume it is an Attr.
pos = key
continue
}
// Can't be either a string or Attr. Definitely an error.
fallthrough
default:
if unknownArg == nil {
pass.ReportRangef(arg, "%s arg %q should be a string or a slog.Attr (possible missing key or value)",
shortName(fn), astutil.Format(pass.Fset, arg))
} else {
pass.ReportRangef(arg, "%s arg %q should probably be a string or a slog.Attr (previous arg %q cannot be a key)",
shortName(fn), astutil.Format(pass.Fset, arg), astutil.Format(pass.Fset, unknownArg))
}
// Stop here so we report at most one missing key per call.
return
}
case value:
// Anything can appear in this position.
// The next position should be a key.
pos = key
case unknown:
// Once we encounter an unknown position, we can never be
// sure if a problem later or at the end of the call is due to a
// missing final value, or a non-key in key position.
// In both cases, unknownArg != nil.
unknownArg = arg
// We don't know what is expected about this position, but all hope is not lost.
if t != stringType && !isAttr(t) && !types.IsInterface(t) {
// This argument is definitely not a key.
//
// unknownArg cannot have been a key, in which case this is the
// corresponding value, and the next position should hold another key.
pos = key
}
}
}
if pos == value {
if unknownArg == nil {
pass.ReportRangef(call, "call to %s missing a final value", shortName(fn))
} else {
pass.ReportRangef(call, "call to %s has a missing or misplaced value", shortName(fn))
}
}
})
return nil, nil
}
func isAttr(t types.Type) bool {
return typesinternal.IsTypeNamed(t, "log/slog", "Attr")
}
// shortName returns a name for the function that is shorter than FullName.
// Examples:
//
// "slog.Info" (instead of "log/slog.Info")
// "slog.Logger.With" (instead of "(*log/slog.Logger).With")
func shortName(fn *types.Func) string {
var r string
if recv := fn.Signature().Recv(); recv != nil {
if _, named := typesinternal.ReceiverNamed(recv); named != nil {
r = named.Obj().Name()
} else {
r = recv.Type().String() // anon struct/interface
}
r += "."
}
return fmt.Sprintf("%s.%s%s", fn.Pkg().Name(), r, fn.Name())
}
// If fn is a slog function that has a ...any parameter for key-value pairs,
// kvFuncSkipArgs returns the number of arguments to skip over to reach the
// corresponding arguments, and true.
// Otherwise it returns (0, false).
func kvFuncSkipArgs(fn *types.Func) (int, bool) {
if pkg := fn.Pkg(); pkg == nil || pkg.Path() != "log/slog" {
return 0, false
}
var recvName string // by default a slog package function
if recv := fn.Signature().Recv(); recv != nil {
_, named := typesinternal.ReceiverNamed(recv)
if named == nil {
return 0, false // anon struct/interface
}
recvName = named.Obj().Name()
}
skip, ok := kvFuncs[recvName][fn.Name()]
return skip, ok
}
// The names of functions and methods in log/slog that take
// ...any for key-value pairs, mapped to the number of initial args to skip in
// order to get to the ones that match the ...any parameter.
// The first key is the dereferenced receiver type name, or "" for a function.
var kvFuncs = map[string]map[string]int{
"": {
"Debug": 1,
"Info": 1,
"Warn": 1,
"Error": 1,
"DebugContext": 2,
"InfoContext": 2,
"WarnContext": 2,
"ErrorContext": 2,
"Log": 3,
"Group": 1,
},
"Logger": {
"Debug": 1,
"Info": 1,
"Warn": 1,
"Error": 1,
"DebugContext": 2,
"InfoContext": 2,
"WarnContext": 2,
"ErrorContext": 2,
"Log": 3,
"With": 0,
},
"Record": {
"Add": 0,
},
}
// isMethodExpr reports whether a call is to a MethodExpr.
func isMethodExpr(info *types.Info, c *ast.CallExpr) bool {
s, ok := c.Fun.(*ast.SelectorExpr)
if !ok {
return false
}
sel := info.Selections[s]
return sel != nil && sel.Kind() == types.MethodExpr
}
================================================
FILE: go/analysis/passes/slog/slog_test.go
================================================
// Copyright 2023 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package slog
import (
"testing"
"golang.org/x/tools/go/analysis/analysistest"
)
func Test(t *testing.T) {
testdata := analysistest.TestData()
analysistest.Run(t, testdata, Analyzer, "a", "b")
}
================================================
FILE: go/analysis/passes/slog/testdata/src/a/a.go
================================================
// Copyright 2023 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// This file contains tests for the slog checker.
//go:build go1.21
package a
import (
"context"
"errors"
"fmt"
"log/slog"
)
func F() {
var (
l *slog.Logger
r slog.Record
)
// Unrelated call.
fmt.Println("ok")
// Valid calls.
slog.Info("msg")
slog.Info("msg", "a", 1)
slog.Info("", "a", 1, "b", "two")
l.Debug("msg", "a", 1)
l.With("a", 1)
slog.Warn("msg", slog.Int("a", 1))
slog.Warn("msg", slog.Int("a", 1), "k", 2)
l.WarnContext(nil, "msg", "a", 1, slog.Int("b", 2), slog.Int("c", 3), "d", 4)
l.DebugContext(nil, "msg", "a", 1, slog.Int("b", 2), slog.Int("c", 3), "d", 4, slog.Int("e", 5))
r.Add("a", 1, "b", 2)
(*slog.Logger).Debug(l, "msg", "a", 1, "b", 2)
var key string
r.Add(key, 1)
// bad
slog.Info("msg", 1) // want `slog.Info arg "1" should be a string or a slog.Attr`
l.Info("msg", 2) // want `slog.Logger.Info arg "2" should be a string or a slog.Attr`
slog.Debug("msg", "a") // want `call to slog.Debug missing a final value`
slog.Warn("msg", slog.Int("a", 1), "k") // want `call to slog.Warn missing a final value`
slog.ErrorContext(nil, "msg", "a", 1, "b") // want `call to slog.ErrorContext missing a final value`
r.Add("K", "v", "k") // want `call to slog.Record.Add missing a final value`
l.With("a", "b", 2) // want `slog.Logger.With arg "2" should be a string or a slog.Attr`
// Report the first problem if there are multiple bad keys.
slog.Debug("msg", "a", 1, 2, 3, 4) // want `slog.Debug arg "2" should be a string or a slog.Attr`
slog.Debug("msg", "a", 1, 2, 3, 4) // want `slog.Debug arg "2" should be a string or a slog.Attr`
slog.Log(nil, slog.LevelWarn, "msg", "a", "b", 2) // want `slog.Log arg "2" should be a string or a slog.Attr`
// Test method expression call.
(*slog.Logger).Debug(l, "msg", "a", 1, 2, 3) // want `slog.Logger.Debug arg "2" should be a string or a slog.Attr`
// Skip calls with spread args.
var args []any
slog.Info("msg", args...)
// Report keys that are statically not exactly "string".
type MyString string
myKey := MyString("a") // any(x) looks like .
slog.Info("", myKey, 1) // want `slog.Info arg "myKey" should be a string or a slog.Attr`
// The variadic part of all the calls below begins with an argument of
// static type any, followed by an integer.
// Even though the we don't know the dynamic type of the first arg, and thus
// whether it is a key, an Attr, or something else, the fact that the
// following integer arg cannot be a key allows us to assume that we should
// expect a key to follow.
var a any = "key"
// This is a valid call for which we correctly produce no diagnostic.
slog.Info("msg", a, 7, "key2", 5)
// This is an invalid call because the final value is missing, but we can't
// be sure that's the reason.
slog.Info("msg", a, 7, "key2") // want `call to slog.Info has a missing or misplaced value`
// Here our guess about the unknown arg (a) is wrong: we assume it's a string, but it's an Attr.
// Therefore the second argument should be a key, but it is a number.
// Ideally our diagnostic would pinpoint the problem, but we don't have enough information.
a = slog.Int("a", 1)
slog.Info("msg", a, 7, "key2") // want `call to slog.Info has a missing or misplaced value`
// This call is invalid for the same reason as the one above, but we can't
// detect that.
slog.Info("msg", a, 7, "key2", 5)
// Another invalid call we can't detect. Here the first argument is wrong.
a = 1
slog.Info("msg", a, 7, "b", 5)
// We can detect the first case as the type of key is UntypedNil,
// e.g. not yet assigned to any and not yet an interface.
// We cannot detect the second.
slog.Debug("msg", nil, 2) // want `slog.Debug arg "nil" should be a string or a slog.Attr`
slog.Debug("msg", any(nil), 2)
// Recovery from unknown value.
slog.Debug("msg", any(nil), "a")
slog.Debug("msg", any(nil), "a", 2)
slog.Debug("msg", any(nil), "a", 2, "b") // want `call to slog.Debug has a missing or misplaced value`
slog.Debug("msg", any(nil), 2, 3, 4) // want "slog.Debug arg \\\"3\\\" should probably be a string or a slog.Attr \\(previous arg \\\"2\\\" cannot be a key\\)"
// In these cases, an argument in key position is an interface, but we can glean useful information about it.
// An error interface in key position is definitely invalid: it can't be a string
// or slog.Attr.
var err error
slog.Error("msg", err) // want `slog.Error arg "err" should be a string or a slog.Attr`
// slog.Attr implements fmt.Stringer, but string does not, so assume the arg is an Attr.
var stringer fmt.Stringer
slog.Info("msg", stringer, "a", 1)
slog.Info("msg", stringer, 1) // want `slog.Info arg "1" should be a string or a slog.Attr`
}
func All() {
// Test all functions and methods at least once.
var (
l *slog.Logger
r slog.Record
ctx context.Context
)
slog.Debug("msg", 1, 2) // want `slog.Debug arg "1" should be a string or a slog.Attr`
slog.Error("msg", 1, 2) // want `slog.Error arg "1" should be a string or a slog.Attr`
slog.Info("msg", 1, 2) // want `slog.Info arg "1" should be a string or a slog.Attr`
slog.Warn("msg", 1, 2) // want `slog.Warn arg "1" should be a string or a slog.Attr`
slog.DebugContext(ctx, "msg", 1, 2) // want `slog.DebugContext arg "1" should be a string or a slog.Attr`
slog.ErrorContext(ctx, "msg", 1, 2) // want `slog.ErrorContext arg "1" should be a string or a slog.Attr`
slog.InfoContext(ctx, "msg", 1, 2) // want `slog.InfoContext arg "1" should be a string or a slog.Attr`
slog.WarnContext(ctx, "msg", 1, 2) // want `slog.WarnContext arg "1" should be a string or a slog.Attr`
slog.Log(ctx, slog.LevelDebug, "msg", 1, 2) // want `slog.Log arg "1" should be a string or a slog.Attr`
l.Debug("msg", 1, 2) // want `slog.Logger.Debug arg "1" should be a string or a slog.Attr`
l.Error("msg", 1, 2) // want `slog.Logger.Error arg "1" should be a string or a slog.Attr`
l.Info("msg", 1, 2) // want `slog.Logger.Info arg "1" should be a string or a slog.Attr`
l.Warn("msg", 1, 2) // want `slog.Logger.Warn arg "1" should be a string or a slog.Attr`
l.DebugContext(ctx, "msg", 1, 2) // want `slog.Logger.DebugContext arg "1" should be a string or a slog.Attr`
l.ErrorContext(ctx, "msg", 1, 2) // want `slog.Logger.ErrorContext arg "1" should be a string or a slog.Attr`
l.InfoContext(ctx, "msg", 1, 2) // want `slog.Logger.InfoContext arg "1" should be a string or a slog.Attr`
l.WarnContext(ctx, "msg", 1, 2) // want `slog.Logger.WarnContext arg "1" should be a string or a slog.Attr`
l.Log(ctx, slog.LevelDebug, "msg", 1, 2) // want `slog.Logger.Log arg "1" should be a string or a slog.Attr`
_ = l.With(1, 2) // want `slog.Logger.With arg "1" should be a string or a slog.Attr`
r.Add(1, 2) // want `slog.Record.Add arg "1" should be a string or a slog.Attr`
_ = slog.Group("key", "a", 1, "b", 2)
_ = slog.Group("key", "a", 1, 2, 3) // want `slog.Group arg "2" should be a string or a slog.Attr`
slog.Error("foo", "err", errors.New("oops")) // regression test for #61228.
}
// Used in tests by package b.
var MyLogger = slog.Default()
================================================
FILE: go/analysis/passes/slog/testdata/src/b/b.go
================================================
// Copyright 2023 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// This file contains tests for the slog checker.
//go:build go1.21
package b
import "a"
func Imported() {
_ = a.MyLogger.With("a", 1, 2, 3) // want `slog.Logger.With arg "2" should be a string or a slog.Attr`
}
================================================
FILE: go/analysis/passes/sortslice/analyzer.go
================================================
// Copyright 2019 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package sortslice defines an Analyzer that checks for calls
// to sort.Slice that do not use a slice type as first argument.
package sortslice
import (
"bytes"
"fmt"
"go/ast"
"go/format"
"go/types"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/ast/inspector"
"golang.org/x/tools/go/types/typeutil"
"golang.org/x/tools/internal/typesinternal"
)
const Doc = `check the argument type of sort.Slice
sort.Slice requires an argument of a slice type. Check that
the interface{} value passed to sort.Slice is actually a slice.`
var Analyzer = &analysis.Analyzer{
Name: "sortslice",
Doc: Doc,
URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/sortslice",
Requires: []*analysis.Analyzer{inspect.Analyzer},
Run: run,
}
func run(pass *analysis.Pass) (any, error) {
if !typesinternal.Imports(pass.Pkg, "sort") {
return nil, nil // doesn't directly import sort
}
inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
nodeFilter := []ast.Node{
(*ast.CallExpr)(nil),
}
inspect.Preorder(nodeFilter, func(n ast.Node) {
call := n.(*ast.CallExpr)
obj := typeutil.Callee(pass.TypesInfo, call)
if !typesinternal.IsFunctionNamed(obj, "sort", "Slice", "SliceStable", "SliceIsSorted") {
return
}
callee := obj.(*types.Func)
arg := call.Args[0]
typ := pass.TypesInfo.Types[arg].Type
if tuple, ok := typ.(*types.Tuple); ok {
typ = tuple.At(0).Type() // special case for Slice(f(...))
}
switch typ.Underlying().(type) {
case *types.Slice, *types.Interface:
return
}
// Restore typ to the original type, we may unwrap the tuple above,
// typ might not be the type of arg.
typ = pass.TypesInfo.Types[arg].Type
var fixes []analysis.SuggestedFix
switch v := typ.Underlying().(type) {
case *types.Array:
var buf bytes.Buffer
format.Node(&buf, pass.Fset, &ast.SliceExpr{
X: arg,
Slice3: false,
Lbrack: arg.End() + 1,
Rbrack: arg.End() + 3,
})
fixes = append(fixes, analysis.SuggestedFix{
Message: "Get a slice of the full array",
TextEdits: []analysis.TextEdit{{
Pos: arg.Pos(),
End: arg.End(),
NewText: buf.Bytes(),
}},
})
case *types.Pointer:
_, ok := v.Elem().Underlying().(*types.Slice)
if !ok {
break
}
var buf bytes.Buffer
format.Node(&buf, pass.Fset, &ast.StarExpr{
X: arg,
})
fixes = append(fixes, analysis.SuggestedFix{
Message: "Dereference the pointer to the slice",
TextEdits: []analysis.TextEdit{{
Pos: arg.Pos(),
End: arg.End(),
NewText: buf.Bytes(),
}},
})
case *types.Signature:
if v.Params().Len() != 0 || v.Results().Len() != 1 {
break
}
if _, ok := v.Results().At(0).Type().Underlying().(*types.Slice); !ok {
break
}
var buf bytes.Buffer
format.Node(&buf, pass.Fset, &ast.CallExpr{
Fun: arg,
})
fixes = append(fixes, analysis.SuggestedFix{
Message: "Call the function",
TextEdits: []analysis.TextEdit{{
Pos: arg.Pos(),
End: arg.End(),
NewText: buf.Bytes(),
}},
})
}
pass.Report(analysis.Diagnostic{
Pos: call.Pos(),
End: call.End(),
Message: fmt.Sprintf("%s's argument must be a slice; is called with %s", callee.FullName(), typ.String()),
SuggestedFixes: fixes,
})
})
return nil, nil
}
================================================
FILE: go/analysis/passes/sortslice/analyzer_test.go
================================================
// Copyright 2019 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package sortslice_test
import (
"testing"
"golang.org/x/tools/go/analysis/analysistest"
"golang.org/x/tools/go/analysis/passes/sortslice"
)
func Test(t *testing.T) {
testdata := analysistest.TestData()
analysistest.Run(t, testdata, sortslice.Analyzer, "a")
}
================================================
FILE: go/analysis/passes/sortslice/testdata/src/a/a.go
================================================
package a
import "sort"
// IncorrectSort tries to sort an integer.
func IncorrectSort() {
i := 5
sortFn := func(i, j int) bool { return false }
sort.Slice(i, sortFn) // want "sort.Slice's argument must be a slice; is called with int"
sort.SliceStable(i, sortFn) // want "sort.SliceStable's argument must be a slice; is called with int"
sort.SliceIsSorted(i, sortFn) // want "sort.SliceIsSorted's argument must be a slice; is called with int"
}
// CorrectSort sorts integers. It should not produce a diagnostic.
func CorrectSort() {
s := []int{2, 3, 5, 6}
sortFn := func(i, j int) bool { return s[i] < s[j] }
sort.Slice(s, sortFn)
sort.SliceStable(s, sortFn)
sort.SliceIsSorted(s, sortFn)
}
// CorrectInterface sorts an interface with a slice
// as the concrete type. It should not produce a diagnostic.
func CorrectInterface() {
var s interface{}
s = interface{}([]int{2, 1, 0})
sortFn := func(i, j int) bool { return s.([]int)[i] < s.([]int)[j] }
sort.Slice(s, sortFn)
sort.SliceStable(s, sortFn)
sort.SliceIsSorted(s, sortFn)
}
type slicecompare interface {
compare(i, j int) bool
}
type intslice []int
func (s intslice) compare(i, j int) bool {
return s[i] < s[j]
}
// UnderlyingInterface sorts an interface with a slice
// as the concrete type. It should not produce a diagnostic.
func UnderlyingInterface() {
var s slicecompare
s = intslice([]int{2, 1, 0})
sort.Slice(s, s.compare)
sort.SliceStable(s, s.compare)
sort.SliceIsSorted(s, s.compare)
}
type mySlice []int
// UnderlyingSlice sorts a type with an underlying type of
// slice of ints. It should not produce a diagnostic.
func UnderlyingSlice() {
s := mySlice{2, 3, 5, 6}
sortFn := func(i, j int) bool { return s[i] < s[j] }
sort.Slice(s, sortFn)
sort.SliceStable(s, sortFn)
sort.SliceIsSorted(s, sortFn)
}
// FunctionResultsAsArguments passes a function which returns two values
// that satisfy sort.Slice signature. It should not produce a diagnostic.
func FunctionResultsAsArguments() {
s := []string{"a", "z", "ooo"}
sort.Slice(less(s))
sort.Slice(lessPtr(s)) // want `sort.Slice's argument must be a slice; is called with \(\*\[\]string,.*`
}
func less(s []string) ([]string, func(i, j int) bool) {
return s, func(i, j int) bool {
return s[i] < s[j]
}
}
func lessPtr(s []string) (*[]string, func(i, j int) bool) {
return &s, func(i, j int) bool {
return s[i] < s[j]
}
}
================================================
FILE: go/analysis/passes/stdmethods/doc.go
================================================
// Copyright 2023 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package stdmethods defines an Analyzer that checks for misspellings
// in the signatures of methods similar to well-known interfaces.
//
// # Analyzer stdmethods
//
// stdmethods: check signature of methods of well-known interfaces
//
// Sometimes a type may be intended to satisfy an interface but may fail to
// do so because of a mistake in its method signature.
// For example, the result of this WriteTo method should be (int64, error),
// not error, to satisfy io.WriterTo:
//
// type myWriterTo struct{...}
// func (myWriterTo) WriteTo(w io.Writer) error { ... }
//
// This check ensures that each method whose name matches one of several
// well-known interface methods from the standard library has the correct
// signature for that interface.
//
// Checked method names include:
//
// Format GobEncode GobDecode MarshalJSON MarshalXML
// Peek ReadByte ReadFrom ReadRune Scan Seek
// UnmarshalJSON UnreadByte UnreadRune WriteByte
// WriteTo
package stdmethods
================================================
FILE: go/analysis/passes/stdmethods/stdmethods.go
================================================
// Copyright 2010 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package stdmethods
import (
_ "embed"
"go/ast"
"go/types"
"strings"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/ast/inspector"
"golang.org/x/tools/internal/analysis/analyzerutil"
)
//go:embed doc.go
var doc string
var Analyzer = &analysis.Analyzer{
Name: "stdmethods",
Doc: analyzerutil.MustExtractDoc(doc, "stdmethods"),
URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/stdmethods",
Requires: []*analysis.Analyzer{inspect.Analyzer},
Run: run,
}
// canonicalMethods lists the input and output types for Go methods
// that are checked using dynamic interface checks. Because the
// checks are dynamic, such methods would not cause a compile error
// if they have the wrong signature: instead the dynamic check would
// fail, sometimes mysteriously. If a method is found with a name listed
// here but not the input/output types listed here, vet complains.
//
// A few of the canonical methods have very common names.
// For example, a type might implement a Scan method that
// has nothing to do with fmt.Scanner, but we still want to check
// the methods that are intended to implement fmt.Scanner.
// To do that, the arguments that have a = prefix are treated as
// signals that the canonical meaning is intended: if a Scan
// method doesn't have a fmt.ScanState as its first argument,
// we let it go. But if it does have a fmt.ScanState, then the
// rest has to match.
var canonicalMethods = map[string]struct{ args, results []string }{
"As": {[]string{"any"}, []string{"bool"}}, // errors.As
// "Flush": {{}, {"error"}}, // http.Flusher and jpeg.writer conflict
"Format": {[]string{"=fmt.State", "rune"}, []string{}}, // fmt.Formatter
"GobDecode": {[]string{"[]byte"}, []string{"error"}}, // gob.GobDecoder
"GobEncode": {[]string{}, []string{"[]byte", "error"}}, // gob.GobEncoder
"Is": {[]string{"error"}, []string{"bool"}}, // errors.Is
"MarshalJSON": {[]string{}, []string{"[]byte", "error"}}, // json.Marshaler
"MarshalXML": {[]string{"*xml.Encoder", "xml.StartElement"}, []string{"error"}}, // xml.Marshaler
"ReadByte": {[]string{}, []string{"byte", "error"}}, // io.ByteReader
"ReadFrom": {[]string{"=io.Reader"}, []string{"int64", "error"}}, // io.ReaderFrom
"ReadRune": {[]string{}, []string{"rune", "int", "error"}}, // io.RuneReader
"Scan": {[]string{"=fmt.ScanState", "rune"}, []string{"error"}}, // fmt.Scanner
"Seek": {[]string{"=int64", "int"}, []string{"int64", "error"}}, // io.Seeker
"UnmarshalJSON": {[]string{"[]byte"}, []string{"error"}}, // json.Unmarshaler
"UnmarshalXML": {[]string{"*xml.Decoder", "xml.StartElement"}, []string{"error"}}, // xml.Unmarshaler
"UnreadByte": {[]string{}, []string{"error"}},
"UnreadRune": {[]string{}, []string{"error"}},
"Unwrap": {[]string{}, []string{"error"}}, // errors.Unwrap
"WriteByte": {[]string{"byte"}, []string{"error"}}, // jpeg.writer (matching bufio.Writer)
"WriteTo": {[]string{"=io.Writer"}, []string{"int64", "error"}}, // io.WriterTo
}
func run(pass *analysis.Pass) (any, error) {
inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
nodeFilter := []ast.Node{
(*ast.FuncDecl)(nil),
(*ast.InterfaceType)(nil),
}
inspect.Preorder(nodeFilter, func(n ast.Node) {
switch n := n.(type) {
case *ast.FuncDecl:
if n.Recv != nil {
canonicalMethod(pass, n.Name)
}
case *ast.InterfaceType:
for _, field := range n.Methods.List {
for _, id := range field.Names {
canonicalMethod(pass, id)
}
}
}
})
return nil, nil
}
func canonicalMethod(pass *analysis.Pass, id *ast.Ident) {
// Expected input/output.
expect, ok := canonicalMethods[id.Name]
if !ok {
return
}
// Actual input/output
sign := pass.TypesInfo.Defs[id].Type().(*types.Signature)
args := sign.Params()
results := sign.Results()
// Special case: WriteTo with more than one argument,
// not trying at all to implement io.WriterTo,
// comes up often enough to skip.
if id.Name == "WriteTo" && args.Len() > 1 {
return
}
// Special case: Is, As and Unwrap only apply when type
// implements error.
if id.Name == "Is" || id.Name == "As" || id.Name == "Unwrap" {
if recv := sign.Recv(); recv == nil || !implementsError(recv.Type()) {
return
}
}
// Special case: Unwrap has two possible signatures.
// Check for Unwrap() []error here.
if id.Name == "Unwrap" {
if args.Len() == 0 && results.Len() == 1 {
t := typeString(results.At(0).Type())
if t == "error" || t == "[]error" {
return
}
}
pass.ReportRangef(id, "method Unwrap() should have signature Unwrap() error or Unwrap() []error")
return
}
// Do the =s (if any) all match?
if !matchParams(expect.args, args, "=") || !matchParams(expect.results, results, "=") {
return
}
// Everything must match.
if !matchParams(expect.args, args, "") || !matchParams(expect.results, results, "") {
expectFmt := id.Name + "(" + argjoin(expect.args) + ")"
if len(expect.results) == 1 {
expectFmt += " " + argjoin(expect.results)
} else if len(expect.results) > 1 {
expectFmt += " (" + argjoin(expect.results) + ")"
}
actual := typeString(sign)
actual = strings.TrimPrefix(actual, "func")
actual = id.Name + actual
pass.ReportRangef(id, "method %s should have signature %s", actual, expectFmt)
}
}
func typeString(typ types.Type) string {
return types.TypeString(typ, (*types.Package).Name)
}
func argjoin(x []string) string {
y := make([]string, len(x))
for i, s := range x {
if s[0] == '=' {
s = s[1:]
}
y[i] = s
}
return strings.Join(y, ", ")
}
// Does each type in expect with the given prefix match the corresponding type in actual?
func matchParams(expect []string, actual *types.Tuple, prefix string) bool {
for i, x := range expect {
if !strings.HasPrefix(x, prefix) {
continue
}
if i >= actual.Len() {
return false
}
if !matchParamType(x, actual.At(i).Type()) {
return false
}
}
if prefix == "" && actual.Len() > len(expect) {
return false
}
return true
}
// Does this one type match?
func matchParamType(expect string, actual types.Type) bool {
expect = strings.TrimPrefix(expect, "=")
// Overkill but easy.
t := typeString(actual)
return t == expect ||
(t == "any" || t == "interface{}") && (expect == "any" || expect == "interface{}")
}
var errorType = types.Universe.Lookup("error").Type().Underlying().(*types.Interface)
func implementsError(actual types.Type) bool {
return types.Implements(actual, errorType)
}
================================================
FILE: go/analysis/passes/stdmethods/stdmethods_test.go
================================================
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package stdmethods_test
import (
"testing"
"golang.org/x/tools/go/analysis/analysistest"
"golang.org/x/tools/go/analysis/passes/stdmethods"
)
func Test(t *testing.T) {
testdata := analysistest.TestData()
analysistest.Run(t, testdata, stdmethods.Analyzer, "a", "typeparams")
}
func TestAnalyzeEncodingXML(t *testing.T) {
analysistest.Run(t, "", stdmethods.Analyzer, "encoding/xml")
}
================================================
FILE: go/analysis/passes/stdmethods/testdata/src/a/a.go
================================================
// Copyright 2010 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package a
import (
"encoding/xml"
"fmt"
"io"
)
type T int
func (T) Scan(x fmt.ScanState, c byte) {} // want `should have signature Scan\(fmt\.ScanState, rune\) error`
func (T) Format(fmt.State, byte) {} // want `should have signature Format\(fmt.State, rune\)`
type U int
func (U) Format(byte) {} // no error: first parameter must be fmt.State to trigger check
func (U) GobDecode() {} // want `should have signature GobDecode\(\[\]byte\) error`
// Test rendering of type names such as xml.Encoder in diagnostic.
func (U) MarshalXML(*xml.Encoder) {} // want `method MarshalXML\(\*xml.Encoder\) should...`
func (U) UnmarshalXML(*xml.Decoder, xml.StartElement) error { // no error: signature matches xml.Unmarshaler
return nil
}
func (U) WriteTo(w io.Writer) {} // want `method WriteTo\(w io.Writer\) should have signature WriteTo\(io.Writer\) \(int64, error\)`
func (T) WriteTo(w io.Writer, more, args int) {} // ok - clearly not io.WriterTo
type I interface {
ReadByte() byte // want `should have signature ReadByte\(\) \(byte, error\)`
}
type V int // V does not implement error.
func (V) As() T { return 0 } // ok - V is not an error
func (V) Is() bool { return false } // ok - V is not an error
func (V) Unwrap() int { return 0 } // ok - V is not an error
type E int
func (E) Error() string { return "" } // E implements error.
func (E) As() {} // want `method As\(\) should have signature As\((any|interface\{\})\) bool`
func (E) Is() {} // want `method Is\(\) should have signature Is\(error\) bool`
func (E) Unwrap() {} // want `method Unwrap\(\) should have signature Unwrap\(\) error or Unwrap\(\) \[\]error`
type F int
func (F) Error() string { return "" } // Both F and *F implement error.
func (*F) As() {} // want `method As\(\) should have signature As\((any|interface\{\})\) bool`
func (*F) Is() {} // want `method Is\(\) should have signature Is\(error\) bool`
func (*F) Unwrap() {} // want `method Unwrap\(\) should have signature Unwrap\(\) error or Unwrap\(\) \[\]error`
type G int
func (G) As(interface{}) bool // ok
type W int
func (W) Error() string { return "" }
func (W) Unwrap() error { return nil } // ok
type M int
func (M) Error() string { return "" }
func (M) Unwrap() []error { return nil } // ok
================================================
FILE: go/analysis/passes/stdmethods/testdata/src/a/b.go
================================================
// Copyright 2021 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package a
type H int
func (H) As(any) bool // ok
================================================
FILE: go/analysis/passes/stdmethods/testdata/src/typeparams/typeparams.go
================================================
// Copyright 2021 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package typeparams
import "fmt"
type T[P any] int
func (T[_]) Scan(x fmt.ScanState, c byte) {} // want `should have signature Scan\(fmt\.ScanState, rune\) error`
func (T[_]) Format(fmt.State, byte) {} // want `should have signature Format\(fmt.State, rune\)`
type U[P any] int
func (U[_]) Format(byte) {} // no error: first parameter must be fmt.State to trigger check
func (U[P]) GobDecode(P) {} // want `should have signature GobDecode\(\[\]byte\) error`
type V[P any] int // V does not implement error.
func (V[_]) As() T[int] { return 0 } // ok - V is not an error
func (V[_]) Is() bool { return false } // ok - V is not an error
func (V[_]) Unwrap() int { return 0 } // ok - V is not an error
type E[P any] int
func (E[_]) Error() string { return "" } // E implements error.
func (E[P]) As() {} // want `method As\(\) should have signature As\((any|interface\{\})\) bool`
func (E[_]) Is() {} // want `method Is\(\) should have signature Is\(error\) bool`
func (E[_]) Unwrap() {} // want `method Unwrap\(\) should have signature Unwrap\(\) error or Unwrap\(\) \[\]error`
type F[P any] int
func (F[_]) Error() string { return "" } // Both F and *F implement error.
func (*F[_]) As() {} // want `method As\(\) should have signature As\((any|interface\{\})\) bool`
func (*F[_]) Is() {} // want `method Is\(\) should have signature Is\(error\) bool`
func (*F[_]) Unwrap() {} // want `method Unwrap\(\) should have signature Unwrap\(\) error or Unwrap\(\) \[\]error`
================================================
FILE: go/analysis/passes/stdversion/main.go
================================================
// Copyright 2024 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build ignore
package main
import (
"golang.org/x/tools/go/analysis/passes/stdversion"
"golang.org/x/tools/go/analysis/singlechecker"
)
func main() { singlechecker.Main(stdversion.Analyzer) }
================================================
FILE: go/analysis/passes/stdversion/stdversion.go
================================================
// Copyright 2024 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package stdversion reports uses of standard library symbols that are
// "too new" for the Go version in force in the referring file.
package stdversion
import (
"go/ast"
"go/build"
"go/types"
"slices"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/ast/inspector"
"golang.org/x/tools/internal/typesinternal"
"golang.org/x/tools/internal/versions"
)
const Doc = `report uses of too-new standard library symbols
The stdversion analyzer reports references to symbols in the standard
library that were introduced by a Go release higher than the one in
force in the referring file. (Recall that the file's Go version is
defined by the 'go' directive its module's go.mod file, or by a
"//go:build go1.X" build tag at the top of the file.)
The analyzer does not report a diagnostic for a reference to a "too
new" field or method of a type that is itself "too new", as this may
have false positives, for example if fields or methods are accessed
through a type alias that is guarded by a Go version constraint.
`
var Analyzer = &analysis.Analyzer{
Name: "stdversion",
Doc: Doc,
Requires: []*analysis.Analyzer{inspect.Analyzer},
URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/stdversion",
RunDespiteErrors: true,
Run: run,
}
func run(pass *analysis.Pass) (any, error) {
// Prior to go1.22, versions.FileVersion returns only the
// toolchain version, which is of no use to us, so
// disable this analyzer on earlier versions.
if !slices.Contains(build.Default.ReleaseTags, "go1.22") {
return nil, nil
}
// Don't report diagnostics for modules marked before go1.21,
// since at that time the go directive wasn't clearly
// specified as a toolchain requirement.
pkgVersion := pass.Pkg.GoVersion()
if !versions.AtLeast(pkgVersion, "go1.21") {
return nil, nil
}
// disallowedSymbols returns the set of standard library symbols
// in a given package that are disallowed at the specified Go version.
type key struct {
pkg *types.Package
version string
}
memo := make(map[key]map[types.Object]string) // records symbol's minimum Go version
disallowedSymbols := func(pkg *types.Package, version string) map[types.Object]string {
k := key{pkg, version}
disallowed, ok := memo[k]
if !ok {
disallowed = typesinternal.TooNewStdSymbols(pkg, version)
memo[k] = disallowed
}
return disallowed
}
// Scan the syntax looking for references to symbols
// that are disallowed by the version of the file.
inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
nodeFilter := []ast.Node{
(*ast.File)(nil),
(*ast.Ident)(nil),
}
var fileVersion string // "" => no check
inspect.Preorder(nodeFilter, func(n ast.Node) {
switch n := n.(type) {
case *ast.File:
if ast.IsGenerated(n) {
// Suppress diagnostics in generated files (such as cgo).
fileVersion = ""
} else {
fileVersion = versions.Lang(versions.FileVersion(pass.TypesInfo, n))
// (may be "" if unknown)
}
case *ast.Ident:
if fileVersion != "" {
if obj, ok := pass.TypesInfo.Uses[n]; ok && obj.Pkg() != nil {
disallowed := disallowedSymbols(obj.Pkg(), fileVersion)
if minVersion, ok := disallowed[origin(obj)]; ok {
// Some symbols are accessible before their release but
// only with specific build tags unknown to us here.
// Avoid false positives in such cases.
// TODO(mkalil): move this check into typesinternal.TooNewStdSymbols.
if obj.Pkg().Path() == "testing/synctest" && versions.AtLeast(fileVersion, "go1.24") {
break // requires go1.24 && goexperiment.synctest || go1.25
}
noun := "module"
if fileVersion != pkgVersion {
noun = "file"
}
pass.ReportRangef(n, "%s.%s requires %v or later (%s is %s)",
obj.Pkg().Name(), obj.Name(), minVersion, noun, fileVersion)
}
}
}
}
})
return nil, nil
}
// origin returns the original uninstantiated symbol for obj.
func origin(obj types.Object) types.Object {
switch obj := obj.(type) {
case *types.Var:
return obj.Origin()
case *types.Func:
return obj.Origin()
case *types.TypeName:
if named, ok := obj.Type().(*types.Named); ok { // (don't unalias)
return named.Origin().Obj()
}
}
return obj
}
================================================
FILE: go/analysis/passes/stdversion/stdversion_test.go
================================================
// Copyright 2024 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package stdversion_test
import (
"path/filepath"
"testing"
"golang.org/x/tools/go/analysis/analysistest"
"golang.org/x/tools/go/analysis/passes/stdversion"
"golang.org/x/tools/internal/testenv"
"golang.org/x/tools/internal/testfiles"
)
func Test(t *testing.T) {
testenv.NeedsGo1Point(t, 23) // TODO(#68658): Waiting on 1.22 backport.
// The test relies on go1.21 std symbols, but the analyzer
// itself requires the go1.22 implementation of versions.FileVersions.
dir := testfiles.ExtractTxtarFileToTmp(t, filepath.Join(analysistest.TestData(), "test.txtar"))
analysistest.Run(t, dir, stdversion.Analyzer,
"example.com/basic",
"example.com/despite",
"example.com/mod20",
"example.com/mod21",
"example.com/mod22",
"example.com/old",
)
}
================================================
FILE: go/analysis/passes/stdversion/testdata/test.txtar
================================================
Test of "too new" diagnostics from the stdversion analyzer.
This test references go1.21 and go1.22 symbols from std.
See also gopls/internal/test/marker/testdata/diagnostics/stdversion.txt
which runs the same test within the gopls analysis driver, to ensure
coverage of per-file Go version support.
-- go.work --
go 1.22
use .
use mod20
use mod21
use mod22
use old
-- go.mod --
module example.com
go 1.21
-- basic/basic.go --
// File version is 1.21.
package basic
import "go/types"
func _() {
// old package-level type
var _ types.Info // ok: defined by go1.0
// new field of older type
_ = new(types.Info).FileVersions // want `types.FileVersions requires go1.22 or later \(module is go1.21\)`
// new method of older type
new(types.Info).PkgNameOf // want `types.PkgNameOf requires go1.22 or later \(module is go1.21\)`
// new package-level type
var a types.Alias // want `types.Alias requires go1.22 or later \(module is go1.21\)`
// new method of new type
a.Underlying() // no diagnostic
}
-- despite/errors.go --
// File version is 1.21.
// Check that RunDespiteErrors is enabled.
package ignore
import "go/types"
func _() {
// report something before the syntax error.
_ = new(types.Info).FileVersions // want `types.FileVersions requires go1.22 or later \(module is go1.21\)`
}
invalid syntax // exercise RunDespiteErrors
-- mod20/go.mod --
module example.com/mod20
go 1.20
-- mod20/notag.go --
// The 1.20 module is before the forward compatibility regime:
// The file's build tag effects selection, but
// not language semantics, so stdversion is silent.
package mod20
import "go/types"
func _() {
var _ types.Alias
}
-- mod20/tag16.go --
// The 1.20 module is before the forward compatibility regime:
// The file's build tag effects selection, but
// not language semantics, so stdversion is silent.
//go:build go1.16
package mod20
import "bytes"
import "go/types"
var _ = bytes.Clone
var _ = types.Alias
-- mod20/tag22.go --
// The 1.20 module is before the forward compatibility regime:
// The file's build tag effects selection, but
// not language semantics, so stdversion is silent.
//go:build go1.22
package mod20
import "bytes"
import "go/types"
var _ = bytes.Clone
var _ = types.Alias
-- mod21/go.mod --
module example.com/mod21
go 1.21
-- mod21/notag.go --
// File version is 1.21.
package mod21
import "go/types"
func _() {
// old package-level type
var _ types.Info // ok: defined by go1.0
// new field of older type
_ = new(types.Info).FileVersions // want `types.FileVersions requires go1.22 or later \(module is go1.21\)`
// new method of older type
new(types.Info).PkgNameOf // want `types.PkgNameOf requires go1.22 or later \(module is go1.21\)`
// new package-level type
var a types.Alias // want `types.Alias requires go1.22 or later \(module is go1.21\)`
// new method of new type
a.Underlying() // no diagnostic
}
-- mod21/tag16.go --
// File version is 1.21.
//
// The module is within the forward compatibility regime so
// the build tag (1.16) can modify the file version, but it cannot
// go below the 1.21 "event horizon" (#68658).
//go:build go1.16
package mod21
import "bytes"
import "go/types"
var _ = bytes.Clone
var _ = types.Alias // want `types.Alias requires go1.22 or later \(module is go1.21\)`
-- mod21/tag22.go --
// File version is 1.22.
//
// The module is within the forward compatibility regime so
// the build tag (1.22) updates the file version to 1.22.
//go:build go1.22
package mod21
import "bytes"
import "go/types"
var _ = bytes.Clone
var _ = types.Alias
-- mod22/go.mod --
module example.com/mod22
go 1.22
-- mod22/notag.go --
// File version is 1.22.
package mod22
import "go/types"
func _() {
var _ = bytes.Clone
var _ = types.Alias
}
-- mod22/tag16.go --
// File version is 1.21.
//
// The module is within the forward compatibility regime so
// the build tag (1.16) can modify the file version, but it cannot
// go below the 1.21 "event horizon" (#68658).
//go:build go1.16
package mod22
import "bytes"
import "go/types"
var _ = bytes.Clone
var _ = types.Alias // want `types.Alias requires go1.22 or later \(file is go1.21\)`
-- old/go.mod --
module example.com/old
go 1.5
-- old/notag.go --
package old
import "go/types"
var _ types.Alias // no diagnostic: go.mod is too old for us to care
-- old/tag21.go --
// The build tag is ignored due to the module version.
//go:build go1.21
package old
import "go/types"
var _ = types.Alias // no diagnostic: go.mod is too old for us to care
================================================
FILE: go/analysis/passes/stringintconv/cmd/stringintconv/main.go
================================================
// Copyright 2020 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// The stringintconv command runs the stringintconv analyzer.
package main
import (
"golang.org/x/tools/go/analysis/passes/stringintconv"
"golang.org/x/tools/go/analysis/singlechecker"
)
func main() { singlechecker.Main(stringintconv.Analyzer) }
================================================
FILE: go/analysis/passes/stringintconv/doc.go
================================================
// Copyright 2023 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package stringintconv defines an Analyzer that flags type conversions
// from integers to strings.
//
// # Analyzer stringintconv
//
// stringintconv: check for string(int) conversions
//
// This checker flags conversions of the form string(x) where x is an integer
// (but not byte or rune) type. Such conversions are discouraged because they
// return the UTF-8 representation of the Unicode code point x, and not a decimal
// string representation of x as one might expect. Furthermore, if x denotes an
// invalid code point, the conversion cannot be statically rejected.
//
// For conversions that intend on using the code point, consider replacing them
// with string(rune(x)). Otherwise, strconv.Itoa and its equivalents return the
// string representation of the value in the desired base.
package stringintconv
================================================
FILE: go/analysis/passes/stringintconv/string.go
================================================
// Copyright 2020 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package stringintconv
import (
_ "embed"
"fmt"
"go/ast"
"go/types"
"strings"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/ast/inspector"
"golang.org/x/tools/internal/analysis/analyzerutil"
"golang.org/x/tools/internal/refactor"
"golang.org/x/tools/internal/typeparams"
"golang.org/x/tools/internal/typesinternal"
)
//go:embed doc.go
var doc string
var Analyzer = &analysis.Analyzer{
Name: "stringintconv",
Doc: analyzerutil.MustExtractDoc(doc, "stringintconv"),
URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/stringintconv",
Requires: []*analysis.Analyzer{inspect.Analyzer},
Run: run,
}
// describe returns a string describing the type typ contained within the type
// set of inType. If non-empty, inName is used as the name of inType (this is
// necessary so that we can use alias type names that may not be reachable from
// inType itself).
func describe(typ, inType types.Type, inName string) string {
name := inName
if typ != inType {
name = typeName(typ)
}
if name == "" {
return ""
}
var parentheticals []string
if underName := typeName(typ.Underlying()); underName != "" && underName != name {
parentheticals = append(parentheticals, underName)
}
if typ != inType && inName != "" && inName != name {
parentheticals = append(parentheticals, "in "+inName)
}
if len(parentheticals) > 0 {
name += " (" + strings.Join(parentheticals, ", ") + ")"
}
return name
}
func typeName(t types.Type) string {
if basic, ok := t.(*types.Basic); ok {
return basic.Name() // may be (e.g.) "untyped int", which has no TypeName
}
if tname := typesinternal.TypeNameFor(t); tname != nil {
return tname.Name()
}
return ""
}
func run(pass *analysis.Pass) (any, error) {
inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
nodeFilter := []ast.Node{
(*ast.File)(nil),
(*ast.CallExpr)(nil),
}
var file *ast.File
inspect.Preorder(nodeFilter, func(n ast.Node) {
if n, ok := n.(*ast.File); ok {
file = n
return
}
call := n.(*ast.CallExpr)
if len(call.Args) != 1 {
return
}
arg := call.Args[0]
// Retrieve target type name.
var tname *types.TypeName
switch fun := call.Fun.(type) {
case *ast.Ident:
tname, _ = pass.TypesInfo.Uses[fun].(*types.TypeName)
case *ast.SelectorExpr:
tname, _ = pass.TypesInfo.Uses[fun.Sel].(*types.TypeName)
}
if tname == nil {
return
}
// In the conversion T(v) of a value v of type V to a target type T, we
// look for types T0 in the type set of T and V0 in the type set of V, such
// that V0->T0 is a problematic conversion. If T and V are not type
// parameters, this amounts to just checking if V->T is a problematic
// conversion.
// First, find a type T0 in T that has an underlying type of string.
T := tname.Type()
ttypes, err := structuralTypes(T)
if err != nil {
return // invalid type
}
var T0 types.Type // string type in the type set of T
for _, tt := range ttypes {
u, _ := tt.Underlying().(*types.Basic)
if u != nil && u.Kind() == types.String {
T0 = tt
break
}
}
if T0 == nil {
// No target types have an underlying type of string.
return
}
// Next, find a type V0 in V that has an underlying integral type that is
// not byte or rune.
V := pass.TypesInfo.TypeOf(arg)
vtypes, err := structuralTypes(V)
if err != nil {
return // invalid type
}
var V0 types.Type // integral type in the type set of V
for _, vt := range vtypes {
u, _ := vt.Underlying().(*types.Basic)
if u != nil && u.Info()&types.IsInteger != 0 {
switch u.Kind() {
case types.Byte, types.Rune, types.UntypedRune:
continue
}
V0 = vt
break
}
}
if V0 == nil {
// No source types are non-byte or rune integer types.
return
}
convertibleToRune := true // if true, we can suggest a fix
for _, t := range vtypes {
if !types.ConvertibleTo(t, types.Typ[types.Rune]) {
convertibleToRune = false
break
}
}
target := describe(T0, T, tname.Name())
source := describe(V0, V, typeName(V))
if target == "" || source == "" {
return // something went wrong
}
diag := analysis.Diagnostic{
Pos: n.Pos(),
Message: fmt.Sprintf("conversion from %s to %s yields a string of one rune, not a string of digits", source, target),
}
addFix := func(message string, edits []analysis.TextEdit) {
diag.SuggestedFixes = append(diag.SuggestedFixes, analysis.SuggestedFix{
Message: message,
TextEdits: edits,
})
}
// Fix 1: use fmt.Sprint(x)
//
// Prefer fmt.Sprint over strconv.Itoa, FormatInt,
// or FormatUint, as it works for any type.
// Add an import of "fmt" as needed.
//
// Unless the type is exactly string, we must retain the conversion.
//
// Do not offer this fix if type parameters are involved,
// as there are too many combinations and subtleties.
// Consider x = rune | int16 | []byte: in all cases,
// string(x) is legal, but the appropriate diagnostic
// and fix differs. Similarly, don't offer the fix if
// the type has methods, as some {String,GoString,Format}
// may change the behavior of fmt.Sprint.
if len(ttypes) == 1 && len(vtypes) == 1 && types.NewMethodSet(V0).Len() == 0 {
prefix, importEdits := refactor.AddImport(pass.TypesInfo, file, "fmt", "fmt", "Sprint", arg.Pos())
if types.Identical(T0, types.Typ[types.String]) {
// string(x) -> fmt.Sprint(x)
addFix("Format the number as a decimal", append(importEdits,
analysis.TextEdit{
Pos: call.Fun.Pos(),
End: call.Fun.End(),
NewText: []byte(prefix + "Sprint"),
}),
)
} else {
// mystring(x) -> mystring(fmt.Sprint(x))
addFix("Format the number as a decimal", append(importEdits,
analysis.TextEdit{
Pos: call.Lparen + 1,
End: call.Lparen + 1,
NewText: []byte(prefix + "Sprint("),
},
analysis.TextEdit{
Pos: call.Rparen,
End: call.Rparen,
NewText: []byte(")"),
}),
)
}
}
// Fix 2: use string(rune(x))
if convertibleToRune {
addFix("Convert a single rune to a string", []analysis.TextEdit{
{
Pos: arg.Pos(),
End: arg.Pos(),
NewText: []byte("rune("),
},
{
Pos: arg.End(),
End: arg.End(),
NewText: []byte(")"),
},
})
}
pass.Report(diag)
})
return nil, nil
}
func structuralTypes(t types.Type) ([]types.Type, error) {
var structuralTypes []types.Type
if tp, ok := types.Unalias(t).(*types.TypeParam); ok {
terms, err := typeparams.StructuralTerms(tp)
if err != nil {
return nil, err
}
for _, term := range terms {
structuralTypes = append(structuralTypes, term.Type())
}
} else {
structuralTypes = append(structuralTypes, t)
}
return structuralTypes, nil
}
================================================
FILE: go/analysis/passes/stringintconv/string_test.go
================================================
// Copyright 2020 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package stringintconv_test
import (
"testing"
"golang.org/x/tools/go/analysis/analysistest"
"golang.org/x/tools/go/analysis/passes/stringintconv"
)
func Test(t *testing.T) {
testdata := analysistest.TestData()
analysistest.Run(t, testdata, stringintconv.Analyzer, "a", "typeparams")
analysistest.RunWithSuggestedFixes(t, testdata, stringintconv.Analyzer, "fix")
}
================================================
FILE: go/analysis/passes/stringintconv/testdata/src/a/a.go
================================================
// Copyright 2020 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// This file contains tests for the stringintconv checker.
package a
type A string
type B = string
type C int
type D = uintptr
func StringTest() {
var (
i int
j rune
k byte
l C
m D
n = []int{0, 1, 2}
o struct{ x int }
)
const p = 0
// First time only, assert the complete message:
_ = string(i) // want `^conversion from int to string yields a string of one rune, not a string of digits$`
_ = string(j)
_ = string(k)
_ = string(p) // want `...from untyped int to string...`
_ = A(l) // want `...from C \(int\) to A \(string\)...`
_ = B(m) // want `...from (uintptr|D \(uintptr\)) to B \(string\)...`
_ = string(n[1]) // want `...from int to string...`
_ = string(o.x) // want `...from int to string...`
}
================================================
FILE: go/analysis/passes/stringintconv/testdata/src/fix/fix.go
================================================
package fix
func _(x uint64) {
println(string(x)) // want `conversion from uint64 to string yields...`
}
================================================
FILE: go/analysis/passes/stringintconv/testdata/src/fix/fix.go.golden
================================================
-- Format the number as a decimal --
package fix
import "fmt"
func _(x uint64) {
println(fmt.Sprint(x)) // want `conversion from uint64 to string yields...`
}
-- Convert a single rune to a string --
package fix
func _(x uint64) {
println(string(rune(x))) // want `conversion from uint64 to string yields...`
}
================================================
FILE: go/analysis/passes/stringintconv/testdata/src/fix/fixdot.go
================================================
package fix
import . "fmt"
func _(x uint64) {
Println(string(x)) // want `conversion from uint64 to string yields...`
}
================================================
FILE: go/analysis/passes/stringintconv/testdata/src/fix/fixdot.go.golden
================================================
-- Format the number as a decimal --
package fix
import . "fmt"
func _(x uint64) {
Println(Sprint(x)) // want `conversion from uint64 to string yields...`
}
-- Convert a single rune to a string --
package fix
import . "fmt"
func _(x uint64) {
Println(string(rune(x))) // want `conversion from uint64 to string yields...`
}
================================================
FILE: go/analysis/passes/stringintconv/testdata/src/fix/fixnamed.go
================================================
package fix
type mystring string
func _(x int16) mystring {
return mystring(x) // want `conversion from int16 to mystring \(string\)...`
}
================================================
FILE: go/analysis/passes/stringintconv/testdata/src/fix/fixnamed.go.golden
================================================
-- Format the number as a decimal --
package fix
import "fmt"
type mystring string
func _(x int16) mystring {
return mystring(fmt.Sprint(x)) // want `conversion from int16 to mystring \(string\)...`
}
-- Convert a single rune to a string --
package fix
type mystring string
func _(x int16) mystring {
return mystring(rune(x)) // want `conversion from int16 to mystring \(string\)...`
}
================================================
FILE: go/analysis/passes/stringintconv/testdata/src/typeparams/typeparams.go
================================================
// Copyright 2021 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package typeparams
type (
Int int
Uintptr = uintptr
String string
)
func _[AllString ~string, MaybeString ~string | ~int, NotString ~int | byte, NamedString String | Int]() {
var (
i int
r rune
b byte
I Int
U uintptr
M MaybeString
N NotString
)
const p = 0
_ = MaybeString(i) // want `conversion from int to string .in MaybeString. yields a string of one rune, not a string of digits`
_ = MaybeString(r)
_ = MaybeString(b)
_ = MaybeString(I) // want `conversion from Int .int. to string .in MaybeString. yields a string of one rune, not a string of digits`
_ = MaybeString(U) // want `conversion from uintptr to string .in MaybeString. yields a string of one rune, not a string of digits`
// Type parameters are never constant types, so arguments are always
// converted to their default type (int versus untyped int, in this case)
_ = MaybeString(p) // want `conversion from int to string .in MaybeString. yields a string of one rune, not a string of digits`
// ...even if the type parameter is only strings.
_ = AllString(p) // want `conversion from int to string .in AllString. yields a string of one rune, not a string of digits`
_ = NotString(i)
_ = NotString(r)
_ = NotString(b)
_ = NotString(I)
_ = NotString(U)
_ = NotString(p)
_ = NamedString(i) // want `conversion from int to String .string, in NamedString. yields a string of one rune, not a string of digits`
_ = string(M) // want `conversion from int .in MaybeString. to string yields a string of one rune, not a string of digits`
// Note that M is not convertible to rune.
_ = MaybeString(M) // want `conversion from int .in MaybeString. to string .in MaybeString. yields a string of one rune, not a string of digits`
_ = NotString(N) // ok
}
================================================
FILE: go/analysis/passes/structtag/structtag.go
================================================
// Copyright 2010 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package structtag defines an Analyzer that checks struct field tags
// are well formed.
package structtag
import (
"errors"
"go/ast"
"go/token"
"go/types"
"path/filepath"
"reflect"
"slices"
"strconv"
"strings"
"fmt"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/ast/inspector"
)
const Doc = `check that struct field tags conform to reflect.StructTag.Get
Also report certain struct tags (json, xml) used with unexported fields.`
var Analyzer = &analysis.Analyzer{
Name: "structtag",
Doc: Doc,
URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/structtag",
Requires: []*analysis.Analyzer{inspect.Analyzer},
RunDespiteErrors: true,
Run: run,
}
func run(pass *analysis.Pass) (any, error) {
inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
nodeFilter := []ast.Node{
(*ast.StructType)(nil),
}
inspect.Preorder(nodeFilter, func(n ast.Node) {
styp, ok := pass.TypesInfo.Types[n.(*ast.StructType)].Type.(*types.Struct)
// Type information may be incomplete.
if !ok {
return
}
var seen namesSeen
for i := 0; i < styp.NumFields(); i++ {
field := styp.Field(i)
tag := styp.Tag(i)
checkCanonicalFieldTag(pass, field, tag, &seen)
}
})
return nil, nil
}
// namesSeen keeps track of encoding tags by their key, name, and nested level
// from the initial struct. The level is taken into account because equal
// encoding key names only conflict when at the same level; otherwise, the lower
// level shadows the higher level.
type namesSeen map[uniqueName]token.Pos
type uniqueName struct {
key string // "xml" or "json"
name string // the encoding name
level int // anonymous struct nesting level
}
func (s *namesSeen) Get(key, name string, level int) (token.Pos, bool) {
if *s == nil {
*s = make(map[uniqueName]token.Pos)
}
pos, ok := (*s)[uniqueName{key, name, level}]
return pos, ok
}
func (s *namesSeen) Set(key, name string, level int, pos token.Pos) {
if *s == nil {
*s = make(map[uniqueName]token.Pos)
}
(*s)[uniqueName{key, name, level}] = pos
}
var checkTagDups = []string{"json", "xml"}
var checkTagSpaces = map[string]bool{"json": true, "xml": true, "asn1": true}
// checkCanonicalFieldTag checks a single struct field tag.
func checkCanonicalFieldTag(pass *analysis.Pass, field *types.Var, tag string, seen *namesSeen) {
if strings.HasPrefix(pass.Pkg.Path(), "encoding/") {
// These packages know how to use their own APIs.
// Sometimes they are testing what happens to incorrect programs.
return
}
for _, key := range checkTagDups {
checkTagDuplicates(pass, tag, key, field, field, seen, 1)
}
if err := validateStructTag(tag); err != nil {
pass.Report(analysis.Diagnostic{
Pos: field.Pos(),
End: field.Pos() + token.Pos(len(field.Name())),
Message: fmt.Sprintf("struct field tag %#q not compatible with reflect.StructTag.Get: %s", tag, err),
})
}
// Check for use of json or xml tags with unexported fields.
// Embedded struct. Nothing to do for now, but that
// may change, depending on what happens with issue 7363.
// TODO(adonovan): investigate, now that issue is fixed.
if field.Anonymous() {
return
}
if field.Exported() {
return
}
for _, enc := range [...]string{"json", "xml"} {
switch reflect.StructTag(tag).Get(enc) {
// Ignore warning if the field not exported and the tag is marked as
// ignored.
case "", "-":
default:
pass.Report(analysis.Diagnostic{
Pos: field.Pos(),
End: field.Pos() + token.Pos(len(field.Name())),
Message: fmt.Sprintf("struct field %s has %s tag but is not exported", field.Name(), enc),
})
return
}
}
}
// checkTagDuplicates checks a single struct field tag to see if any tags are
// duplicated. nearest is the field that's closest to the field being checked,
// while still being part of the top-level struct type.
func checkTagDuplicates(pass *analysis.Pass, tag, key string, nearest, field *types.Var, seen *namesSeen, level int) {
val := reflect.StructTag(tag).Get(key)
if val == "-" {
// Ignored, even if the field is anonymous.
return
}
if val == "" || val[0] == ',' {
if !field.Anonymous() {
// Ignored if the field isn't anonymous.
return
}
typ, ok := field.Type().Underlying().(*types.Struct)
if !ok {
return
}
for i := 0; i < typ.NumFields(); i++ {
field := typ.Field(i)
if !field.Exported() {
continue
}
tag := typ.Tag(i)
checkTagDuplicates(pass, tag, key, nearest, field, seen, level+1)
}
return
}
if key == "xml" && field.Name() == "XMLName" {
// XMLName defines the XML element name of the struct being
// checked. That name cannot collide with element or attribute
// names defined on other fields of the struct. Vet does not have a
// check for untagged fields of type struct defining their own name
// by containing a field named XMLName; see issue 18256.
return
}
if i := strings.Index(val, ","); i >= 0 {
if key == "xml" {
// Use a separate namespace for XML attributes.
if slices.Contains(strings.Split(val[i:], ","), "attr") {
key += " attribute" // Key is part of the error message.
}
}
val = val[:i]
}
if pos, ok := seen.Get(key, val, level); ok {
alsoPos := pass.Fset.Position(pos)
alsoPos.Column = 0
// Make the "also at" position relative to the current position,
// to ensure that all warnings are unambiguous and correct. For
// example, via anonymous struct fields, it's possible for the
// two fields to be in different packages and directories.
thisPos := pass.Fset.Position(field.Pos())
rel, err := filepath.Rel(filepath.Dir(thisPos.Filename), alsoPos.Filename)
if err != nil {
// Possibly because the paths are relative; leave the
// filename alone.
} else {
alsoPos.Filename = rel
}
pass.Report(analysis.Diagnostic{
Pos: nearest.Pos(),
End: nearest.Pos() + token.Pos(len(nearest.Name())),
Message: fmt.Sprintf("struct field %s repeats %s tag %q also at %s", field.Name(), key, val, alsoPos),
})
} else {
seen.Set(key, val, level, field.Pos())
}
}
var (
errTagSyntax = errors.New("bad syntax for struct tag pair")
errTagKeySyntax = errors.New("bad syntax for struct tag key")
errTagValueSyntax = errors.New("bad syntax for struct tag value")
errTagValueSpace = errors.New("suspicious space in struct tag value")
errTagSpace = errors.New("key:\"value\" pairs not separated by spaces")
)
// validateStructTag parses the struct tag and returns an error if it is not
// in the canonical format, which is a space-separated list of key:"value"
// settings. The value may contain spaces.
func validateStructTag(tag string) error {
// This code is based on the StructTag.Get code in package reflect.
n := 0
for ; tag != ""; n++ {
if n > 0 && tag != "" && tag[0] != ' ' {
// More restrictive than reflect, but catches likely mistakes
// like `x:"foo",y:"bar"`, which parses as `x:"foo" ,y:"bar"` with second key ",y".
return errTagSpace
}
// Skip leading space.
i := 0
for i < len(tag) && tag[i] == ' ' {
i++
}
tag = tag[i:]
if tag == "" {
break
}
// Scan to colon. A space, a quote or a control character is a syntax error.
// Strictly speaking, control chars include the range [0x7f, 0x9f], not just
// [0x00, 0x1f], but in practice, we ignore the multi-byte control characters
// as it is simpler to inspect the tag's bytes than the tag's runes.
i = 0
for i < len(tag) && tag[i] > ' ' && tag[i] != ':' && tag[i] != '"' && tag[i] != 0x7f {
i++
}
if i == 0 {
return errTagKeySyntax
}
if i+1 >= len(tag) || tag[i] != ':' {
return errTagSyntax
}
if tag[i+1] != '"' {
return errTagValueSyntax
}
key := tag[:i]
tag = tag[i+1:]
// Scan quoted string to find value.
i = 1
for i < len(tag) && tag[i] != '"' {
if tag[i] == '\\' {
i++
}
i++
}
if i >= len(tag) {
return errTagValueSyntax
}
qvalue := tag[:i+1]
tag = tag[i+1:]
value, err := strconv.Unquote(qvalue)
if err != nil {
return errTagValueSyntax
}
if !checkTagSpaces[key] {
continue
}
switch key {
case "xml":
// If the first or last character in the XML tag is a space, it is
// suspicious.
if strings.Trim(value, " ") != value {
return errTagValueSpace
}
// If there are multiple spaces, they are suspicious.
if strings.Count(value, " ") > 1 {
return errTagValueSpace
}
// If there is no comma, skip the rest of the checks.
comma := strings.IndexRune(value, ',')
if comma < 0 {
continue
}
// If the character before a comma is a space, this is suspicious.
if comma > 0 && value[comma-1] == ' ' {
return errTagValueSpace
}
value = value[comma+1:]
case "json":
// JSON allows using spaces in the name, so skip it.
comma := strings.IndexRune(value, ',')
if comma < 0 {
continue
}
value = value[comma+1:]
}
if strings.IndexByte(value, ' ') >= 0 {
return errTagValueSpace
}
}
return nil
}
================================================
FILE: go/analysis/passes/structtag/structtag_test.go
================================================
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package structtag_test
import (
"testing"
"golang.org/x/tools/go/analysis/analysistest"
"golang.org/x/tools/go/analysis/passes/structtag"
)
func Test(t *testing.T) {
testdata := analysistest.TestData()
analysistest.Run(t, testdata, structtag.Analyzer, "a")
}
================================================
FILE: go/analysis/passes/structtag/testdata/src/a/a.go
================================================
// Copyright 2010 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// This file contains the test for canonical struct tags.
package a
import (
"a/b"
"encoding/xml"
)
type StructTagTest struct {
A int "hello" // want "`hello` not compatible with reflect.StructTag.Get: bad syntax for struct tag pair"
B int "\tx:\"y\"" // want "not compatible with reflect.StructTag.Get: bad syntax for struct tag key"
C int "x:\"y\"\tx:\"y\"" // want "not compatible with reflect.StructTag.Get"
D int "x:`y`" // want "not compatible with reflect.StructTag.Get: bad syntax for struct tag value"
E int "ct\brl:\"char\"" // want "not compatible with reflect.StructTag.Get: bad syntax for struct tag pair"
F int `:"emptykey"` // want "not compatible with reflect.StructTag.Get: bad syntax for struct tag key"
G int `x:"noEndQuote` // want "not compatible with reflect.StructTag.Get: bad syntax for struct tag value"
H int `x:"trunc\x0"` // want "not compatible with reflect.StructTag.Get: bad syntax for struct tag value"
I int `x:"foo",y:"bar"` // want "not compatible with reflect.StructTag.Get: key:.value. pairs not separated by spaces"
J int `x:"foo"y:"bar"` // want "not compatible with reflect.StructTag.Get: key:.value. pairs not separated by spaces"
OK0 int `x:"y" u:"v" w:""`
OK1 int `x:"y:z" u:"v" w:""` // note multiple colons.
OK2 int "k0:\"values contain spaces\" k1:\"literal\ttabs\" k2:\"and\\tescaped\\tabs\""
OK3 int `under_scores:"and" CAPS:"ARE_OK"`
}
type UnexportedEncodingTagTest struct {
x int `json:"xx"` // want "struct field x has json tag but is not exported"
y int `xml:"yy"` // want "struct field y has xml tag but is not exported"
z int
A int `json:"aa" xml:"bb"`
b int `json:"-"`
C int `json:"-"`
}
type unexp struct{}
type JSONEmbeddedField struct {
UnexportedEncodingTagTest `is:"embedded"`
unexp `is:"embedded,notexported" json:"unexp"` // OK for now, see issue 7363
}
type AnonymousJSON struct{}
type AnonymousXML struct{}
type AnonymousJSONField struct {
DuplicateAnonJSON int `json:"a"`
A int "hello" // want "`hello` not compatible with reflect.StructTag.Get: bad syntax for struct tag pair"
}
// With different names to allow using as anonymous fields multiple times.
type AnonymousJSONField2 struct {
DuplicateAnonJSON int `json:"a"`
}
type AnonymousJSONField3 struct {
DuplicateAnonJSON int `json:"a"`
}
type DuplicateJSONFields struct {
JSON int `json:"a"`
DuplicateJSON int `json:"a"` // want "struct field DuplicateJSON repeats json tag .a. also at a.go:66"
IgnoredJSON int `json:"-"`
OtherIgnoredJSON int `json:"-"`
OmitJSON int `json:",omitempty"`
OtherOmitJSON int `json:",omitempty"`
DuplicateOmitJSON int `json:"a,omitempty"` // want "struct field DuplicateOmitJSON repeats json tag .a. also at a.go:66"
NonJSON int `foo:"a"`
DuplicateNonJSON int `foo:"a"`
Embedded struct {
DuplicateJSON int `json:"a"` // OK because it's not in the same struct type
}
AnonymousJSON `json:"a"` // want "struct field AnonymousJSON repeats json tag .a. also at a.go:66"
XML int `xml:"a"`
DuplicateXML int `xml:"a"` // want "struct field DuplicateXML repeats xml tag .a. also at a.go:80"
IgnoredXML int `xml:"-"`
OtherIgnoredXML int `xml:"-"`
OmitXML int `xml:",omitempty"`
OtherOmitXML int `xml:",omitempty"`
DuplicateOmitXML int `xml:"a,omitempty"` // want "struct field DuplicateOmitXML repeats xml tag .a. also at a.go:80"
NonXML int `foo:"a"`
DuplicateNonXML int `foo:"a"`
Embedded2 struct {
DuplicateXML int `xml:"a"` // OK because it's not in the same struct type
}
AnonymousXML `xml:"a"` // want "struct field AnonymousXML repeats xml tag .a. also at a.go:80"
Attribute struct {
XMLName xml.Name `xml:"b"`
NoDup int `xml:"b"` // OK because XMLName above affects enclosing struct.
Attr int `xml:"b,attr"` // OK because 0 is valid.
DupAttr int `xml:"b,attr"` // want "struct field DupAttr repeats xml attribute tag .b. also at a.go:96"
DupOmitAttr int `xml:"b,omitempty,attr"` // want "struct field DupOmitAttr repeats xml attribute tag .b. also at a.go:96"
AnonymousXML `xml:"b,attr"` // want "struct field AnonymousXML repeats xml attribute tag .b. also at a.go:96"
}
AnonymousJSONField2 `json:"not_anon"` // ok; fields aren't embedded in JSON
AnonymousJSONField3 `json:"-"` // ok; entire field is ignored in JSON
}
type UnexpectedSpacetest struct {
A int `json:"a,omitempty"`
B int `json:"b, omitempty"` // want "suspicious space in struct tag value"
C int `json:"c ,omitempty"`
D int `json:"d,omitempty, string"` // want "suspicious space in struct tag value"
E int `xml:"e local"`
F int `xml:"f "` // want "suspicious space in struct tag value"
G int `xml:" g"` // want "suspicious space in struct tag value"
H int `xml:"h ,omitempty"` // want "suspicious space in struct tag value"
I int `xml:"i, omitempty"` // want "suspicious space in struct tag value"
J int `xml:"j local ,omitempty"` // want "suspicious space in struct tag value"
K int `xml:"k local, omitempty"` // want "suspicious space in struct tag value"
L int `xml:" l local,omitempty"` // want "suspicious space in struct tag value"
M int `xml:"m local,omitempty"` // want "suspicious space in struct tag value"
N int `xml:" "` // want "suspicious space in struct tag value"
O int `xml:""`
P int `xml:","`
Q int `foo:" doesn't care "`
}
// Nested fields can be shadowed by fields further up. For example,
// ShadowingAnonJSON replaces the json:"a" field in AnonymousJSONField.
// However, if the two conflicting fields appear at the same level like in
// DuplicateWithAnotherPackage, we should error.
type ShadowingJsonFieldName struct {
AnonymousJSONField
ShadowingAnonJSON int `json:"a"`
}
type DuplicateWithAnotherPackage struct {
b.AnonymousJSONField
AnonymousJSONField2 // want "struct field DuplicateAnonJSON repeats json tag .a. also at b.b.go:8"
}
================================================
FILE: go/analysis/passes/structtag/testdata/src/a/b/b.go
================================================
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package b
type AnonymousJSONField struct {
DuplicateAnonJSON int `json:"a"`
}
================================================
FILE: go/analysis/passes/testinggoroutine/doc.go
================================================
// Copyright 2023 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package testinggoroutine defines an Analyzerfor detecting calls to
// Fatal from a test goroutine.
//
// # Analyzer testinggoroutine
//
// testinggoroutine: report calls to (*testing.T).Fatal from goroutines started by a test
//
// Functions that abruptly terminate a test, such as the Fatal, Fatalf, FailNow, and
// Skip{,f,Now} methods of *testing.T, must be called from the test goroutine itself.
// This checker detects calls to these functions that occur within a goroutine
// started by the test. For example:
//
// func TestFoo(t *testing.T) {
// go func() {
// t.Fatal("oops") // error: (*T).Fatal called from non-test goroutine
// }()
// }
package testinggoroutine
================================================
FILE: go/analysis/passes/testinggoroutine/testdata/src/a/a.go
================================================
// Copyright 2020 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package a
import (
"log"
"sync"
"testing"
)
func TestBadFatalf(t *testing.T) {
var wg sync.WaitGroup
defer wg.Wait()
for i := 0; i < 2; i++ {
wg.Add(1)
go func(id int) {
defer wg.Done()
t.Fatalf("TestFailed: id = %v\n", id) // want "call to .+T.+Fatalf from a non-test goroutine"
}(i)
}
}
func TestOKErrorf(t *testing.T) {
var wg sync.WaitGroup
defer wg.Wait()
for i := 0; i < 2; i++ {
wg.Add(1)
go func(id int) {
defer wg.Done()
t.Errorf("TestFailed: id = %v\n", id)
}(i)
}
}
func TestBadFatal(t *testing.T) {
var wg sync.WaitGroup
defer wg.Wait()
for i := 0; i < 2; i++ {
wg.Add(1)
go func(id int) {
defer wg.Done()
t.Fatal("TestFailed") // want "call to .+T.+Fatal from a non-test goroutine"
}(i)
}
}
func f(t *testing.T, _ string) {
t.Fatal("TestFailed")
}
func g() {}
func TestBadFatalIssue47470(t *testing.T) {
go f(t, "failed test 1") // want "call to .+T.+Fatal from a non-test goroutine"
g := func(t *testing.T, _ string) {
t.Fatal("TestFailed")
}
go g(t, "failed test 2") // want "call to .+T.+Fatal from a non-test goroutine"
}
func BenchmarkBadFatalf(b *testing.B) {
var wg sync.WaitGroup
defer wg.Wait()
for i := 0; i < b.N; i++ {
wg.Add(1)
go func(id int) {
defer wg.Done()
b.Fatalf("TestFailed: id = %v\n", id) // want "call to .+B.+Fatalf from a non-test goroutine"
}(i)
}
}
func BenchmarkBadFatal(b *testing.B) {
var wg sync.WaitGroup
defer wg.Wait()
for i := 0; i < b.N; i++ {
wg.Add(1)
go func(id int) {
defer wg.Done()
b.Fatal("TestFailed") // want "call to .+B.+Fatal from a non-test goroutine"
}(i)
}
}
func BenchmarkOKErrorf(b *testing.B) {
var wg sync.WaitGroup
defer wg.Wait()
for i := 0; i < b.N; i++ {
wg.Add(1)
go func(id int) {
defer wg.Done()
b.Errorf("TestFailed: %d", i)
}(i)
}
}
func BenchmarkBadFatalGoGo(b *testing.B) {
var wg sync.WaitGroup
defer wg.Wait()
for i := 0; i < b.N; i++ {
wg.Add(1)
go func(id int) {
go func() {
defer wg.Done()
b.Fatal("TestFailed") // want "call to .+B.+Fatal from a non-test goroutine"
}()
}(i)
}
if false {
defer b.Fatal("here")
}
if true {
go func() {
b.Fatal("in here") // want "call to .+B.+Fatal from a non-test goroutine"
}()
}
func() {
func() {
func() {
func() {
go func() {
b.Fatal("Here") // want "call to .+B.+Fatal from a non-test goroutine"
}()
}()
}()
}()
}()
_ = 10 * 10
_ = func() bool {
go b.Fatal("Failed") // want "call to .+B.+Fatal from a non-test goroutine"
return true
}
defer func() {
go b.Fatal("Here") // want "call to .+B.+Fatal from a non-test goroutine"
}()
}
func BenchmarkBadSkip(b *testing.B) {
for i := 0; i < b.N; i++ {
if i == 100 {
go b.Skip("Skipping") // want "call to .+B.+Skip from a non-test goroutine"
}
if i == 22 {
go func() {
go func() {
b.Skip("Skipping now") // want "call to .+B.+Skip from a non-test goroutine"
}()
}()
}
}
}
func TestBadSkip(t *testing.T) {
for i := 0; i < 1000; i++ {
if i == 100 {
go t.Skip("Skipping") // want "call to .+T.+Skip from a non-test goroutine"
}
if i == 22 {
go func() {
go func() {
t.Skip("Skipping now") // want "call to .+T.+Skip from a non-test goroutine"
}()
}()
}
}
}
func BenchmarkBadFailNow(b *testing.B) {
for i := 0; i < b.N; i++ {
if i == 100 {
go b.FailNow() // want "call to .+B.+FailNow from a non-test goroutine"
}
if i == 22 {
go func() {
go func() {
b.FailNow() // want "call to .+B.+FailNow from a non-test goroutine"
}()
}()
}
}
}
func TestBadFailNow(t *testing.T) {
for i := 0; i < 1000; i++ {
if i == 100 {
go t.FailNow() // want "call to .+T.+FailNow from a non-test goroutine"
}
if i == 22 {
go func() {
go func() {
t.FailNow() // want "call to .+T.+FailNow from a non-test goroutine"
}()
}()
}
}
}
func TestBadWithLoopCond(ty *testing.T) {
var wg sync.WaitGroup
defer wg.Wait()
for i := 0; i < 10; i++ {
wg.Add(1)
go func(id int) {
defer ty.Fatalf("Why") // want "call to .+T.+Fatalf from a non-test goroutine"
go func() {
for j := 0; j < 2; ty.FailNow() { // want "call to .+T.+FailNow from"
j++
ty.Errorf("Done here")
}
}()
}(i)
}
}
type customType int
func (ct *customType) Fatalf(fmtSpec string, args ...interface{}) {
if fmtSpec == "" {
panic("empty format specifier")
}
}
func (ct *customType) FailNow() {}
func (ct *customType) Skip() {}
func TestWithLogFatalf(t *testing.T) {
var wg sync.WaitGroup
defer wg.Wait()
for i := 0; i < 10; i++ {
wg.Add(1)
go func(id int) {
go func() {
for j := 0; j < 2; j++ {
log.Fatal("Done here")
}
}()
}(i)
}
}
func TestWithCustomType(t *testing.T) {
var wg sync.WaitGroup
defer wg.Wait()
ct := new(customType)
defer ct.FailNow()
defer ct.Skip()
for i := 0; i < 10; i++ {
wg.Add(1)
go func(id int) {
go func() {
for j := 0; j < 2; j++ {
ct.Fatalf("Done here: %d", i)
}
}()
}(i)
}
}
func helpTB(tb testing.TB) {
tb.FailNow()
}
func TestTB(t *testing.T) {
go helpTB(t) // want "call to .+TB.+FailNow from a non-test goroutine"
}
func TestIssue48124(t *testing.T) {
go helper(t) // want "call to .+T.+Skip from a non-test goroutine"
}
func TestEachCall(t *testing.T) {
go helper(t) // want "call to .+T.+Skip from a non-test goroutine"
go helper(t) // want "call to .+T.+Skip from a non-test goroutine"
}
func TestWithSubtest(t *testing.T) {
t.Run("name", func(t2 *testing.T) {
t.FailNow() // want "call to .+T.+FailNow on t defined outside of the subtest"
t2.Fatal()
})
f := func(t3 *testing.T) {
t.FailNow()
t3.Fatal()
}
t.Run("name", f) // want "call to .+T.+FailNow on t defined outside of the subtest"
g := func(t4 *testing.T) {
t.FailNow()
t4.Fatal()
}
g(t)
t.Run("name", helper)
go t.Run("name", func(t2 *testing.T) {
t.FailNow() // want "call to .+T.+FailNow on t defined outside of the subtest"
t2.Fatal()
})
}
func TestMultipleVariables(t *testing.T) {
{ // short decl
f, g := func(t1 *testing.T) {
t1.Fatal()
}, func(t2 *testing.T) {
t2.Error()
}
go f(t) // want "call to .+T.+Fatal from a non-test goroutine"
go g(t)
t.Run("name", f)
t.Run("name", g)
}
{ // var decl
var f, g = func(t1 *testing.T) {
t1.Fatal()
}, func(t2 *testing.T) {
t2.Error()
}
go f(t) // want "call to .+T.+Fatal from a non-test goroutine"
go g(t)
t.Run("name", f)
t.Run("name", g)
}
}
func BadIgnoresMultipleAssignments(t *testing.T) {
{
f := func(t1 *testing.T) {
t1.Fatal()
}
go f(t) // want "call to .+T.+Fatal from a non-test goroutine"
f = func(t2 *testing.T) {
t2.Error()
}
go f(t) // want "call to .+T.+Fatal from a non-test goroutine"
}
{
f := func(t1 *testing.T) {
t1.Error()
}
go f(t)
f = func(t2 *testing.T) {
t2.FailNow()
}
go f(t) // false negative
}
}
func TestGoDoesNotDescendIntoSubtest(t *testing.T) {
f := func(t2 *testing.T) {
g := func(t3 *testing.T) {
t3.Fatal() // fine
}
t2.Run("name", g)
t2.FailNow() // bad
}
go f(t) // want "call to .+T.+FailNow from a non-test goroutine"
}
func TestFreeVariableAssignedWithinEnclosing(t *testing.T) {
f := func(t2 *testing.T) {
inner := t
inner.FailNow()
}
go f(nil) // want "call to .+T.+FailNow from a non-test goroutine"
t.Run("name", func(t3 *testing.T) {
go f(nil) // want "call to .+T.+FailNow from a non-test goroutine"
})
// Without pointer analysis we cannot tell if inner is t or t2.
// So we accept a false negatives on the following examples.
t.Run("name", f)
go func(_ *testing.T) {
t.Run("name", f)
}(nil)
go t.Run("name", f)
}
func TestWithUnusedSelection(t *testing.T) {
go func() {
_ = t.FailNow
}()
t.Run("name", func(t2 *testing.T) {
_ = t.FailNow
})
}
func TestMethodExprsAreIgnored(t *testing.T) {
go func() {
(*testing.T).FailNow(t)
}()
}
func TestRecursive(t *testing.T) {
t.SkipNow()
go TestRecursive(t) // want "call to .+T.+SkipNow from a non-test goroutine"
t.Run("name", TestRecursive)
}
func TestMethodSelection(t *testing.T) {
var h helperType
go h.help(t) // want "call to .+T.+SkipNow from a non-test goroutine"
t.Run("name", h.help)
}
type helperType struct{}
func (h *helperType) help(t *testing.T) { t.SkipNow() }
func TestIssue63799a(t *testing.T) {
done := make(chan struct{})
go func() {
defer close(done)
t.Run("", func(t *testing.T) {
t.Fatal() // No warning. This is in a subtest.
})
}()
<-done
}
func TestIssue63799b(t *testing.T) {
// Simplified from go.dev/cl/538698
// nondet is some unspecified boolean placeholder.
var nondet func() bool
t.Run("nohup", func(t *testing.T) {
if nondet() {
t.Skip("ignored")
}
go t.Run("nohup-i", func(t *testing.T) {
t.Parallel()
if nondet() {
if nondet() {
t.Skip("go.dev/cl/538698 wanted to have skip here")
}
t.Error("ignored")
} else {
t.Log("ignored")
}
})
})
}
func TestIssue63849(t *testing.T) {
go func() {
helper(t) // False negative. We do not do an actual interprodecural reachability analysis.
}()
go helper(t) // want "call to .+T.+Skip from a non-test goroutine"
}
================================================
FILE: go/analysis/passes/testinggoroutine/testdata/src/a/b.go
================================================
// Copyright 2020 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package a
import "testing"
func helper(t *testing.T) {
t.Skip()
}
================================================
FILE: go/analysis/passes/testinggoroutine/testdata/src/typeparams/typeparams.go
================================================
// Copyright 2021 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package typeparams
import (
"testing"
)
func f[P any](t *testing.T) {
t.Fatal("failed")
}
func TestBadFatalf[P any](t *testing.T) {
go f[int](t) // want "call to .+T.+Fatal from a non-test goroutine"
}
================================================
FILE: go/analysis/passes/testinggoroutine/testinggoroutine.go
================================================
// Copyright 2020 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package testinggoroutine
import (
_ "embed"
"fmt"
"go/ast"
"go/token"
"go/types"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/ast/inspector"
"golang.org/x/tools/go/types/typeutil"
"golang.org/x/tools/internal/analysis/analyzerutil"
"golang.org/x/tools/internal/typesinternal"
)
//go:embed doc.go
var doc string
var reportSubtest bool
func init() {
Analyzer.Flags.BoolVar(&reportSubtest, "subtest", false, "whether to check if t.Run subtest is terminated correctly; experimental")
}
var Analyzer = &analysis.Analyzer{
Name: "testinggoroutine",
Doc: analyzerutil.MustExtractDoc(doc, "testinggoroutine"),
URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/testinggoroutine",
Requires: []*analysis.Analyzer{inspect.Analyzer},
Run: run,
}
func run(pass *analysis.Pass) (any, error) {
inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
if !typesinternal.Imports(pass.Pkg, "testing") {
return nil, nil
}
toDecl := localFunctionDecls(pass.TypesInfo, pass.Files)
// asyncs maps nodes whose statements will be executed concurrently
// with respect to some test function, to the call sites where they
// are invoked asynchronously. There may be multiple such call sites
// for e.g. test helpers.
asyncs := make(map[ast.Node][]*asyncCall)
var regions []ast.Node
addCall := func(c *asyncCall) {
if c != nil {
r := c.region
if asyncs[r] == nil {
regions = append(regions, r)
}
asyncs[r] = append(asyncs[r], c)
}
}
// Collect all of the go callee() and t.Run(name, callee) extents.
inspect.Nodes([]ast.Node{
(*ast.FuncDecl)(nil),
(*ast.GoStmt)(nil),
(*ast.CallExpr)(nil),
}, func(node ast.Node, push bool) bool {
if !push {
return false
}
switch node := node.(type) {
case *ast.FuncDecl:
return hasBenchmarkOrTestParams(node)
case *ast.GoStmt:
c := goAsyncCall(pass.TypesInfo, node, toDecl)
addCall(c)
case *ast.CallExpr:
c := tRunAsyncCall(pass.TypesInfo, node)
addCall(c)
}
return true
})
// Check for t.Forbidden() calls within each region r that is a
// callee in some go r() or a t.Run("name", r).
//
// Also considers a special case when r is a go t.Forbidden() call.
for _, region := range regions {
ast.Inspect(region, func(n ast.Node) bool {
if n == region {
return true // always descend into the region itself.
} else if asyncs[n] != nil {
return false // will be visited by another region.
}
call, ok := n.(*ast.CallExpr)
if !ok {
return true
}
x, sel, fn := forbiddenMethod(pass.TypesInfo, call)
if x == nil {
return true
}
for _, e := range asyncs[region] {
if !withinScope(e.scope, x) {
forbidden := formatMethod(sel, fn) // e.g. "(*testing.T).Forbidden
var context string
var where analysis.Range = e.async // Put the report at the go fun() or t.Run(name, fun).
if _, local := e.fun.(*ast.FuncLit); local {
where = call // Put the report at the t.Forbidden() call.
} else if id, ok := e.fun.(*ast.Ident); ok {
context = fmt.Sprintf(" (%s calls %s)", id.Name, forbidden)
}
if _, ok := e.async.(*ast.GoStmt); ok {
pass.ReportRangef(where, "call to %s from a non-test goroutine%s", forbidden, context)
} else if reportSubtest {
pass.ReportRangef(where, "call to %s on %s defined outside of the subtest%s", forbidden, x.Name(), context)
}
}
}
return true
})
}
return nil, nil
}
func hasBenchmarkOrTestParams(fnDecl *ast.FuncDecl) bool {
// Check that the function's arguments include "*testing.T" or "*testing.B".
params := fnDecl.Type.Params.List
for _, param := range params {
if _, ok := typeIsTestingDotTOrB(param.Type); ok {
return true
}
}
return false
}
func typeIsTestingDotTOrB(expr ast.Expr) (string, bool) {
starExpr, ok := expr.(*ast.StarExpr)
if !ok {
return "", false
}
selExpr, ok := starExpr.X.(*ast.SelectorExpr)
if !ok {
return "", false
}
varPkg := selExpr.X.(*ast.Ident)
if varPkg.Name != "testing" {
return "", false
}
varTypeName := selExpr.Sel.Name
ok = varTypeName == "B" || varTypeName == "T"
return varTypeName, ok
}
// asyncCall describes a region of code that needs to be checked for
// t.Forbidden() calls as it is started asynchronously from an async
// node go fun() or t.Run(name, fun).
type asyncCall struct {
region ast.Node // region of code to check for t.Forbidden() calls.
async ast.Node // *ast.GoStmt or *ast.CallExpr (for t.Run)
scope ast.Node // Report t.Forbidden() if t is not declared within scope.
fun ast.Expr // fun in go fun() or t.Run(name, fun)
}
// withinScope returns true if x.Pos() is in [scope.Pos(), scope.End()].
func withinScope(scope ast.Node, x *types.Var) bool {
if scope != nil {
return x.Pos() != token.NoPos && scope.Pos() <= x.Pos() && x.Pos() <= scope.End()
}
return false
}
// goAsyncCall returns the extent of a call from a go fun() statement.
func goAsyncCall(info *types.Info, goStmt *ast.GoStmt, toDecl func(*types.Func) *ast.FuncDecl) *asyncCall {
call := goStmt.Call
fun := ast.Unparen(call.Fun)
if id := typesinternal.UsedIdent(info, fun); id != nil {
if lit := funcLitInScope(id); lit != nil {
return &asyncCall{region: lit, async: goStmt, scope: nil, fun: fun}
}
}
if fn := typeutil.StaticCallee(info, call); fn != nil { // static call or method in the package?
if decl := toDecl(fn); decl != nil {
return &asyncCall{region: decl, async: goStmt, scope: nil, fun: fun}
}
}
// Check go statement for go t.Forbidden() or go func(){t.Forbidden()}().
return &asyncCall{region: goStmt, async: goStmt, scope: nil, fun: fun}
}
// tRunAsyncCall returns the extent of a call from a t.Run("name", fun) expression.
func tRunAsyncCall(info *types.Info, call *ast.CallExpr) *asyncCall {
if len(call.Args) != 2 {
return nil
}
run := typeutil.Callee(info, call)
if run, ok := run.(*types.Func); !ok || !isMethodNamed(run, "testing", "Run") {
return nil
}
fun := ast.Unparen(call.Args[1])
if lit, ok := fun.(*ast.FuncLit); ok { // function lit?
return &asyncCall{region: lit, async: call, scope: lit, fun: fun}
}
if id := typesinternal.UsedIdent(info, fun); id != nil {
if lit := funcLitInScope(id); lit != nil { // function lit in variable?
return &asyncCall{region: lit, async: call, scope: lit, fun: fun}
}
}
// Check within t.Run(name, fun) for calls to t.Forbidden,
// e.g. t.Run(name, func(t *testing.T){ t.Forbidden() })
return &asyncCall{region: call, async: call, scope: fun, fun: fun}
}
var forbidden = []string{
"FailNow",
"Fatal",
"Fatalf",
"Skip",
"Skipf",
"SkipNow",
}
// forbiddenMethod decomposes a call x.m() into (x, x.m, m) where
// x is a variable, x.m is a selection, and m is the static callee m.
// Returns (nil, nil, nil) if call is not of this form.
func forbiddenMethod(info *types.Info, call *ast.CallExpr) (*types.Var, *types.Selection, *types.Func) {
// Compare to typeutil.StaticCallee.
fun := ast.Unparen(call.Fun)
selExpr, ok := fun.(*ast.SelectorExpr)
if !ok {
return nil, nil, nil
}
sel := info.Selections[selExpr]
if sel == nil {
return nil, nil, nil
}
var x *types.Var
if id, ok := ast.Unparen(selExpr.X).(*ast.Ident); ok {
x, _ = info.Uses[id].(*types.Var)
}
if x == nil {
return nil, nil, nil
}
fn, _ := sel.Obj().(*types.Func)
if fn == nil || !isMethodNamed(fn, "testing", forbidden...) {
return nil, nil, nil
}
return x, sel, fn
}
func formatMethod(sel *types.Selection, fn *types.Func) string {
var ptr string
rtype := sel.Recv()
if p, ok := types.Unalias(rtype).(*types.Pointer); ok {
ptr = "*"
rtype = p.Elem()
}
return fmt.Sprintf("(%s%s).%s", ptr, rtype.String(), fn.Name())
}
================================================
FILE: go/analysis/passes/testinggoroutine/testinggoroutine_test.go
================================================
// Copyright 2020 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package testinggoroutine_test
import (
"testing"
"golang.org/x/tools/go/analysis/analysistest"
"golang.org/x/tools/go/analysis/passes/testinggoroutine"
)
func init() {
testinggoroutine.Analyzer.Flags.Set("subtest", "true")
}
func Test(t *testing.T) {
testdata := analysistest.TestData()
pkgs := []string{"a", "typeparams"}
analysistest.Run(t, testdata, testinggoroutine.Analyzer, pkgs...)
}
================================================
FILE: go/analysis/passes/testinggoroutine/util.go
================================================
// Copyright 2023 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package testinggoroutine
import (
"go/ast"
"go/types"
"slices"
)
// AST and types utilities that not specific to testinggoroutines.
// localFunctionDecls returns a mapping from *types.Func to *ast.FuncDecl in files.
func localFunctionDecls(info *types.Info, files []*ast.File) func(*types.Func) *ast.FuncDecl {
var fnDecls map[*types.Func]*ast.FuncDecl // computed lazily
return func(f *types.Func) *ast.FuncDecl {
if f != nil && fnDecls == nil {
fnDecls = make(map[*types.Func]*ast.FuncDecl)
for _, file := range files {
for _, decl := range file.Decls {
if fnDecl, ok := decl.(*ast.FuncDecl); ok {
if fn, ok := info.Defs[fnDecl.Name].(*types.Func); ok {
fnDecls[fn] = fnDecl
}
}
}
}
}
// TODO: set f = f.Origin() here.
return fnDecls[f]
}
}
// isMethodNamed returns true if f is a method defined
// in package with the path pkgPath with a name in names.
//
// (Unlike [analysis.IsMethodNamed], it ignores the receiver type name.)
func isMethodNamed(f *types.Func, pkgPath string, names ...string) bool {
if f == nil {
return false
}
if f.Pkg() == nil || f.Pkg().Path() != pkgPath {
return false
}
if f.Signature().Recv() == nil {
return false
}
return slices.Contains(names, f.Name())
}
// funcLitInScope returns a FuncLit that id is at least initially assigned to.
//
// TODO: This is closely tied to id.Obj which is deprecated.
func funcLitInScope(id *ast.Ident) *ast.FuncLit {
// Compare to (*ast.Object).Pos().
if id.Obj == nil {
return nil
}
var rhs ast.Expr
switch d := id.Obj.Decl.(type) {
case *ast.AssignStmt:
for i, x := range d.Lhs {
if ident, isIdent := x.(*ast.Ident); isIdent && ident.Name == id.Name && i < len(d.Rhs) {
rhs = d.Rhs[i]
}
}
case *ast.ValueSpec:
for i, n := range d.Names {
if n.Name == id.Name && i < len(d.Values) {
rhs = d.Values[i]
}
}
}
lit, _ := rhs.(*ast.FuncLit)
return lit
}
================================================
FILE: go/analysis/passes/tests/doc.go
================================================
// Copyright 2023 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package tests defines an Analyzer that checks for common mistaken
// usages of tests and examples.
//
// # Analyzer tests
//
// tests: check for common mistaken usages of tests and examples
//
// The tests checker walks Test, Benchmark, Fuzzing and Example functions checking
// malformed names, wrong signatures and examples documenting non-existent
// identifiers.
//
// Please see the documentation for package testing in golang.org/pkg/testing
// for the conventions that are enforced for Tests, Benchmarks, and Examples.
package tests
================================================
FILE: go/analysis/passes/tests/testdata/src/a/a.go
================================================
package a
func Foo() {}
================================================
FILE: go/analysis/passes/tests/testdata/src/a/a_test.go
================================================
package a
import (
"testing"
)
// Buf is a ...
type Buf []byte
// Append ...
func (*Buf) Append([]byte) {}
func (Buf) Reset() {}
func (Buf) Len() int { return 0 }
// DefaultBuf is a ...
var DefaultBuf Buf
func Example() {} // OK because is package-level.
func Example_goodSuffix() {} // OK because refers to suffix annotation.
func Example_BadSuffix() {} // want "Example_BadSuffix has malformed example suffix: BadSuffix"
func ExampleBuf() {} // OK because refers to known top-level type.
func ExampleBuf_Append() {} // OK because refers to known method.
func ExampleBuf_Clear() {} // want "ExampleBuf_Clear refers to unknown field or method: Buf.Clear"
func ExampleBuf_suffix() {} // OK because refers to suffix annotation.
func ExampleBuf_Append_Bad() {} // want "ExampleBuf_Append_Bad has malformed example suffix: Bad"
func ExampleBuf_Append_suffix() {} // OK because refers to known method with valid suffix.
func ExampleDefaultBuf() {} // OK because refers to top-level identifier.
func ExampleBuf_Reset() bool { return true } // want "ExampleBuf_Reset should return nothing"
func ExampleBuf_Len(i int) {} // want "ExampleBuf_Len should be niladic"
// "Puffer" is German for "Buffer".
func ExamplePuffer() {} // want "ExamplePuffer refers to unknown identifier: Puffer"
func ExamplePuffer_Append() {} // want "ExamplePuffer_Append refers to unknown identifier: Puffer"
func ExamplePuffer_suffix() {} // want "ExamplePuffer_suffix refers to unknown identifier: Puffer"
func ExampleFoo() {} // OK because a.Foo exists
func ExampleBar() {} // want "ExampleBar refers to unknown identifier: Bar"
func Example_withOutput() {
// Output:
// meow
} // OK because output is the last comment block
func Example_withBadOutput() {
// Output: // want "output comment block must be the last comment block"
// meow
// todo: change to bark
}
func Example_withBadUnorderedOutput() {
// Unordered Output: // want "output comment block must be the last comment block"
// meow
// todo: change to bark
}
func Example_withCommentAfterFunc() {
// Output: // OK because it is the last comment block
// meow
} // todo: change to bark
func Example_withOutputCommentAfterFunc() {
// Output:
// meow
} // Output: bark // OK because output is not inside of an example
func Example_withMultipleOutputs() {
// Output: // want "there can only be one output comment block per example"
// meow
// Output: // want "there can only be one output comment block per example"
// bark
// Output: // OK because it is the last output comment block
// ribbit
}
func nonTest() {} // OK because it doesn't start with "Test".
func (Buf) TesthasReceiver() {} // OK because it has a receiver.
func TestOKSuffix(*testing.T) {} // OK because first char after "Test" is Uppercase.
func TestÜnicodeWorks(*testing.T) {} // OK because the first char after "Test" is Uppercase.
func TestbadSuffix(*testing.T) {} // want "first letter after 'Test' must not be lowercase"
func TestemptyImportBadSuffix(*testing.T) {} // want "first letter after 'Test' must not be lowercase"
func Test(*testing.T) {} // OK "Test" on its own is considered a test.
func Testify() {} // OK because it takes no parameters.
func TesttooManyParams(*testing.T, string) {} // OK because it takes too many parameters.
func TesttooManyNames(a, b *testing.T) {} // OK because it takes too many names.
func TestnoTParam(string) {} // OK because it doesn't take a *testing.T
func BenchmarkbadSuffix(*testing.B) {} // want "first letter after 'Benchmark' must not be lowercase"
================================================
FILE: go/analysis/passes/tests/testdata/src/a/ax_test.go
================================================
package a_test
import _ "a"
func ExampleFoo() {} // OK because a.Foo exists
func ExampleBar() {} // want "ExampleBar refers to unknown identifier: Bar"
================================================
FILE: go/analysis/passes/tests/testdata/src/a/go118_test.go
================================================
package a
import (
"testing"
)
func Fuzzfoo(*testing.F) {} // want "first letter after 'Fuzz' must not be lowercase"
func FuzzBoo(*testing.F) {} // OK because first letter after 'Fuzz' is Uppercase.
func FuzzCallDifferentFunc(f *testing.F) {
f.Name() //OK
}
func FuzzFunc(f *testing.F) {
f.Fuzz(func(t *testing.T) {}) // OK "first argument is of type *testing.T"
}
func FuzzFuncWithArgs(f *testing.F) {
f.Add() // want `wrong number of values in call to \(\*testing.F\)\.Add: 0, fuzz target expects 2`
f.Add(1, 2, 3, 4) // want `wrong number of values in call to \(\*testing.F\)\.Add: 4, fuzz target expects 2`
f.Add(5, 5) // want `mismatched type in call to \(\*testing.F\)\.Add: int, fuzz target expects \[\]byte`
f.Add([]byte("hello"), 5) // want `mismatched types in call to \(\*testing.F\)\.Add: \[\[\]byte int\], fuzz target expects \[int \[\]byte\]`
f.Add(5, []byte("hello")) // OK
f.Fuzz(func(t *testing.T, i int, b []byte) { // OK "arguments in func are allowed"
f.Add(5, []byte("hello")) // want `fuzz target must not call any \*F methods`
f.Name() // OK "calls to (*F).Failed and (*F).Name are allowed"
f.Failed() // OK "calls to (*F).Failed and (*F).Name are allowed"
f.Fuzz(func(t *testing.T) {}) // want `fuzz target must not call any \*F methods`
})
}
func FuzzArgFunc(f *testing.F) {
f.Fuzz(0) // want "argument to Fuzz must be a function"
}
func FuzzFuncWithReturn(f *testing.F) {
f.Fuzz(func(t *testing.T) bool { return true }) // want "fuzz target must not return any value"
}
func FuzzFuncNoArg(f *testing.F) {
f.Fuzz(func() {}) // want "fuzz target must have 1 or more argument"
}
func FuzzFuncFirstArgNotTesting(f *testing.F) {
f.Fuzz(func(i int64) {}) // want "the first parameter of a fuzz target must be \\*testing.T"
}
func FuzzFuncFirstArgTestingNotT(f *testing.F) {
f.Fuzz(func(t *testing.F) {}) // want "the first parameter of a fuzz target must be \\*testing.T"
}
func FuzzFuncSecondArgNotAllowed(f *testing.F) {
f.Fuzz(func(t *testing.T, i complex64) {}) // want "fuzzing arguments can only have the following types: string, bool, float32, float64, int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, \\[\\]byte"
}
func FuzzFuncSecondArgArrNotAllowed(f *testing.F) {
f.Fuzz(func(t *testing.T, i []int) {}) // want "fuzzing arguments can only have the following types: string, bool, float32, float64, int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, \\[\\]byte"
}
func FuzzFuncConsecutiveArgNotAllowed(f *testing.F) {
f.Fuzz(func(t *testing.T, i, j string, k complex64) {}) // want "fuzzing arguments can only have the following types: string, bool, float32, float64, int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, \\[\\]byte"
}
func FuzzFuncInner(f *testing.F) {
innerFunc := func(t *testing.T, i float32) {}
f.Fuzz(innerFunc) // ok
}
func FuzzArrayOfFunc(f *testing.F) {
var funcs = []func(t *testing.T, i int){func(t *testing.T, i int) {}}
f.Fuzz(funcs[0]) // ok
}
type GenericSlice[T any] []T
func FuzzGenericFunc(f *testing.F) {
g := GenericSlice[func(t *testing.T, i int)]{func(t *testing.T, i int) {}}
f.Fuzz(g[0]) // ok
}
type F func(t *testing.T, i int32)
type myType struct {
myVar F
}
func FuzzObjectMethod(f *testing.F) {
obj := myType{
myVar: func(t *testing.T, i int32) {},
}
f.Fuzz(obj.myVar) // ok
}
// Test for golang/go#56505: checking fuzz arguments should not panic on *error.
func FuzzIssue56505(f *testing.F) {
f.Fuzz(func(e *error) {}) // want "the first parameter of a fuzz target must be \\*testing.T"
}
================================================
FILE: go/analysis/passes/tests/testdata/src/b/b.go
================================================
package b
type Foo struct {
}
func (f *Foo) F() {
}
================================================
FILE: go/analysis/passes/tests/testdata/src/b_x_test/b_test.go
================================================
package b_x_test
import (
"a"
"b"
)
func ExampleFoo_F() {
var x b.Foo
x.F()
a.Foo()
}
func ExampleFoo_G() { // want "ExampleFoo_G refers to unknown field or method: Foo.G"
}
func ExampleBar_F() { // want "ExampleBar_F refers to unknown identifier: Bar"
}
================================================
FILE: go/analysis/passes/tests/testdata/src/divergent/buf.go
================================================
// Test of examples with divergent packages.
// Package buf ...
package buf
// Buf is a ...
type Buf []byte
// Append ...
func (*Buf) Append([]byte) {}
func (Buf) Reset() {}
func (Buf) Len() int { return 0 }
// DefaultBuf is a ...
var DefaultBuf Buf
================================================
FILE: go/analysis/passes/tests/testdata/src/divergent/buf_test.go
================================================
// Test of examples with divergent packages.
package buf
func Example() {} // OK because is package-level.
func Example_suffix() {} // OK because refers to suffix annotation.
func Example_BadSuffix() {} // want "Example_BadSuffix has malformed example suffix: BadSuffix"
func ExampleBuf() {} // OK because refers to known top-level type.
func ExampleBuf_Append() {} // OK because refers to known method.
func ExampleBuf_Clear() {} // want "ExampleBuf_Clear refers to unknown field or method: Buf.Clear"
func ExampleBuf_suffix() {} // OK because refers to suffix annotation.
func ExampleBuf_Append_Bad() {} // want "ExampleBuf_Append_Bad has malformed example suffix: Bad"
func ExampleBuf_Append_suffix() {} // OK because refers to known method with valid suffix.
func ExampleDefaultBuf() {} // OK because refers to top-level identifier.
func ExampleBuf_Reset() bool { return true } // want "ExampleBuf_Reset should return nothing"
func ExampleBuf_Len(i int) {} // want "ExampleBuf_Len should be niladic"
// "Puffer" is German for "Buffer".
func ExamplePuffer() {} // want "ExamplePuffer refers to unknown identifier: Puffer"
func ExamplePuffer_Append() {} // want "ExamplePuffer_Append refers to unknown identifier: Puffer"
func ExamplePuffer_suffix() {} // want "ExamplePuffer_suffix refers to unknown identifier: Puffer"
================================================
FILE: go/analysis/passes/tests/testdata/src/typeparams/typeparams.go
================================================
// Copyright 2021 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package typeparams
func Zero[T any]() T {
var zero T
return zero
}
================================================
FILE: go/analysis/passes/tests/testdata/src/typeparams/typeparams_test.go
================================================
// Copyright 2021 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package typeparams
import "testing"
func Test(*testing.T) {
_ = Zero[int]() // It is fine to use generics within tests.
}
// Note: We format {Test,Benchmark}typeParam with a 't' in "type" to avoid an error from
// cmd/go/internal/load. That package can also give an error about Test and Benchmark
// functions with TypeParameters. These tests may need to be updated if that logic changes.
func TesttypeParam[T any](*testing.T) {} // want "TesttypeParam has type parameters: it will not be run by go test as a TestXXX function" "TesttypeParam has malformed name"
func BenchmarktypeParam[T any](*testing.B) {} // want "BenchmarktypeParam has type parameters: it will not be run by go test as a BenchmarkXXX function" "BenchmarktypeParam has malformed name"
func ExampleZero[T any]() { // want "ExampleZero should not have type params"
print(Zero[T]())
}
================================================
FILE: go/analysis/passes/tests/tests.go
================================================
// Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package tests
import (
_ "embed"
"go/ast"
"go/token"
"go/types"
"regexp"
"strings"
"unicode"
"unicode/utf8"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/internal/analysis/analyzerutil"
"golang.org/x/tools/internal/astutil"
"golang.org/x/tools/internal/typesinternal"
)
//go:embed doc.go
var doc string
var Analyzer = &analysis.Analyzer{
Name: "tests",
Doc: analyzerutil.MustExtractDoc(doc, "tests"),
URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/tests",
Run: run,
}
var acceptedFuzzTypes = []types.Type{
types.Typ[types.String],
types.Typ[types.Bool],
types.Typ[types.Float32],
types.Typ[types.Float64],
types.Typ[types.Int],
types.Typ[types.Int8],
types.Typ[types.Int16],
types.Typ[types.Int32],
types.Typ[types.Int64],
types.Typ[types.Uint],
types.Typ[types.Uint8],
types.Typ[types.Uint16],
types.Typ[types.Uint32],
types.Typ[types.Uint64],
types.NewSlice(types.Universe.Lookup("byte").Type()),
}
func run(pass *analysis.Pass) (any, error) {
for _, f := range pass.Files {
if !strings.HasSuffix(pass.Fset.File(f.FileStart).Name(), "_test.go") {
continue
}
for _, decl := range f.Decls {
fn, ok := decl.(*ast.FuncDecl)
if !ok || fn.Recv != nil {
// Ignore non-functions or functions with receivers.
continue
}
switch {
case strings.HasPrefix(fn.Name.Name, "Example"):
checkExampleName(pass, fn)
checkExampleOutput(pass, fn, f.Comments)
case strings.HasPrefix(fn.Name.Name, "Test"):
checkTest(pass, fn, "Test")
case strings.HasPrefix(fn.Name.Name, "Benchmark"):
checkTest(pass, fn, "Benchmark")
case strings.HasPrefix(fn.Name.Name, "Fuzz"):
checkTest(pass, fn, "Fuzz")
checkFuzz(pass, fn)
}
}
}
return nil, nil
}
// checkFuzz checks the contents of a fuzz function.
func checkFuzz(pass *analysis.Pass, fn *ast.FuncDecl) {
params := checkFuzzCall(pass, fn)
if params != nil {
checkAddCalls(pass, fn, params)
}
}
// checkFuzzCall checks the arguments of f.Fuzz() calls:
//
// 1. f.Fuzz() should call a function and it should be of type (*testing.F).Fuzz().
// 2. The called function in f.Fuzz(func(){}) should not return result.
// 3. First argument of func() should be of type *testing.T
// 4. Second argument onwards should be of type []byte, string, bool, byte,
// rune, float32, float64, int, int8, int16, int32, int64, uint, uint8, uint16,
// uint32, uint64
// 5. func() must not call any *F methods, e.g. (*F).Log, (*F).Error, (*F).Skip
// The only *F methods that are allowed in the (*F).Fuzz function are (*F).Failed and (*F).Name.
//
// Returns the list of parameters to the fuzz function, if they are valid fuzz parameters.
func checkFuzzCall(pass *analysis.Pass, fn *ast.FuncDecl) (params *types.Tuple) {
ast.Inspect(fn, func(n ast.Node) bool {
call, ok := n.(*ast.CallExpr)
if ok {
if !isFuzzTargetDotFuzz(pass, call) {
return true
}
// Only one argument (func) must be passed to (*testing.F).Fuzz.
if len(call.Args) != 1 {
return true
}
expr := call.Args[0]
if pass.TypesInfo.Types[expr].Type == nil {
return true
}
t := pass.TypesInfo.Types[expr].Type.Underlying()
tSign, argOk := t.(*types.Signature)
// Argument should be a function
if !argOk {
pass.ReportRangef(expr, "argument to Fuzz must be a function")
return false
}
// ff Argument function should not return
if tSign.Results().Len() != 0 {
pass.ReportRangef(expr, "fuzz target must not return any value")
}
// ff Argument function should have 1 or more argument
if tSign.Params().Len() == 0 {
pass.ReportRangef(expr, "fuzz target must have 1 or more argument")
return false
}
ok := validateFuzzArgs(pass, tSign.Params(), expr)
if ok && params == nil {
params = tSign.Params()
}
// Inspect the function that was passed as an argument to make sure that
// there are no calls to *F methods, except for Name and Failed.
ast.Inspect(expr, func(n ast.Node) bool {
if call, ok := n.(*ast.CallExpr); ok {
if !isFuzzTargetDot(pass, call, "") {
return true
}
if !isFuzzTargetDot(pass, call, "Name") && !isFuzzTargetDot(pass, call, "Failed") {
pass.ReportRangef(call, "fuzz target must not call any *F methods")
}
}
return true
})
// We do not need to look at any calls to f.Fuzz inside of a Fuzz call,
// since they are not allowed.
return false
}
return true
})
return params
}
// checkAddCalls checks that the arguments of f.Add calls have the same number and type of arguments as
// the signature of the function passed to (*testing.F).Fuzz
func checkAddCalls(pass *analysis.Pass, fn *ast.FuncDecl, params *types.Tuple) {
ast.Inspect(fn, func(n ast.Node) bool {
call, ok := n.(*ast.CallExpr)
if ok {
if !isFuzzTargetDotAdd(pass, call) {
return true
}
// The first argument to function passed to (*testing.F).Fuzz is (*testing.T).
if len(call.Args) != params.Len()-1 {
pass.ReportRangef(call, "wrong number of values in call to (*testing.F).Add: %d, fuzz target expects %d", len(call.Args), params.Len()-1)
return true
}
var mismatched []int
for i, expr := range call.Args {
if pass.TypesInfo.Types[expr].Type == nil {
return true
}
t := pass.TypesInfo.Types[expr].Type
if !types.Identical(t, params.At(i+1).Type()) {
mismatched = append(mismatched, i)
}
}
// If just one of the types is mismatched report for that
// type only. Otherwise report for the whole call to (*testing.F).Add
if len(mismatched) == 1 {
i := mismatched[0]
expr := call.Args[i]
t := pass.TypesInfo.Types[expr].Type
pass.ReportRangef(expr, "mismatched type in call to (*testing.F).Add: %v, fuzz target expects %v", t, params.At(i+1).Type())
} else if len(mismatched) > 1 {
var gotArgs, wantArgs []types.Type
for i := 0; i < len(call.Args); i++ {
gotArgs, wantArgs = append(gotArgs, pass.TypesInfo.Types[call.Args[i]].Type), append(wantArgs, params.At(i+1).Type())
}
pass.ReportRangef(call, "mismatched types in call to (*testing.F).Add: %v, fuzz target expects %v", gotArgs, wantArgs)
}
}
return true
})
}
// isFuzzTargetDotFuzz reports whether call is (*testing.F).Fuzz().
func isFuzzTargetDotFuzz(pass *analysis.Pass, call *ast.CallExpr) bool {
return isFuzzTargetDot(pass, call, "Fuzz")
}
// isFuzzTargetDotAdd reports whether call is (*testing.F).Add().
func isFuzzTargetDotAdd(pass *analysis.Pass, call *ast.CallExpr) bool {
return isFuzzTargetDot(pass, call, "Add")
}
// isFuzzTargetDot reports whether call is (*testing.F).().
func isFuzzTargetDot(pass *analysis.Pass, call *ast.CallExpr, name string) bool {
if selExpr, ok := call.Fun.(*ast.SelectorExpr); ok {
if !isTestingType(pass.TypesInfo.Types[selExpr.X].Type, "F") {
return false
}
if name == "" || selExpr.Sel.Name == name {
return true
}
}
return false
}
// Validate the arguments of fuzz target.
func validateFuzzArgs(pass *analysis.Pass, params *types.Tuple, expr ast.Expr) bool {
fLit, isFuncLit := expr.(*ast.FuncLit)
exprRange := expr
ok := true
if !isTestingType(params.At(0).Type(), "T") {
if isFuncLit {
exprRange = fLit.Type.Params.List[0].Type
}
pass.ReportRangef(exprRange, "the first parameter of a fuzz target must be *testing.T")
ok = false
}
for i := 1; i < params.Len(); i++ {
if !isAcceptedFuzzType(params.At(i).Type()) {
if isFuncLit {
curr := 0
for _, field := range fLit.Type.Params.List {
curr += len(field.Names)
if i < curr {
exprRange = field.Type
break
}
}
}
pass.ReportRangef(exprRange, "fuzzing arguments can only have the following types: %s", formatAcceptedFuzzType())
ok = false
}
}
return ok
}
func isTestingType(typ types.Type, testingType string) bool {
// No Unalias here: I doubt "go test" recognizes
// "type A = *testing.T; func Test(A) {}" as a test.
ptr, ok := typ.(*types.Pointer)
if !ok {
return false
}
return typesinternal.IsTypeNamed(ptr.Elem(), "testing", testingType)
}
// Validate that fuzz target function's arguments are of accepted types.
func isAcceptedFuzzType(paramType types.Type) bool {
for _, typ := range acceptedFuzzTypes {
if types.Identical(typ, paramType) {
return true
}
}
return false
}
func formatAcceptedFuzzType() string {
var acceptedFuzzTypesStrings []string
for _, typ := range acceptedFuzzTypes {
acceptedFuzzTypesStrings = append(acceptedFuzzTypesStrings, typ.String())
}
acceptedFuzzTypesMsg := strings.Join(acceptedFuzzTypesStrings, ", ")
return acceptedFuzzTypesMsg
}
func isExampleSuffix(s string) bool {
r, size := utf8.DecodeRuneInString(s)
return size > 0 && unicode.IsLower(r)
}
func isTestSuffix(name string) bool {
if len(name) == 0 {
// "Test" is ok.
return true
}
r, _ := utf8.DecodeRuneInString(name)
return !unicode.IsLower(r)
}
func isTestParam(typ ast.Expr, wantType string) bool {
ptr, ok := typ.(*ast.StarExpr)
if !ok {
// Not a pointer.
return false
}
// No easy way of making sure it's a *testing.T or *testing.B:
// ensure the name of the type matches.
if name, ok := ptr.X.(*ast.Ident); ok {
return name.Name == wantType
}
if sel, ok := ptr.X.(*ast.SelectorExpr); ok {
return sel.Sel.Name == wantType
}
return false
}
func lookup(pkg *types.Package, name string) []types.Object {
if o := pkg.Scope().Lookup(name); o != nil {
return []types.Object{o}
}
var ret []types.Object
// Search through the imports to see if any of them define name.
// It's hard to tell in general which package is being tested, so
// for the purposes of the analysis, allow the object to appear
// in any of the imports. This guarantees there are no false positives
// because the example needs to use the object so it must be defined
// in the package or one if its imports. On the other hand, false
// negatives are possible, but should be rare.
for _, imp := range pkg.Imports() {
if obj := imp.Scope().Lookup(name); obj != nil {
ret = append(ret, obj)
}
}
return ret
}
// This pattern is taken from /go/src/go/doc/example.go
var outputRe = regexp.MustCompile(`(?i)^[[:space:]]*(unordered )?output:`)
type commentMetadata struct {
isOutput bool
pos token.Pos
}
func checkExampleOutput(pass *analysis.Pass, fn *ast.FuncDecl, fileComments []*ast.CommentGroup) {
commentsInExample := []commentMetadata{}
numOutputs := 0
// Find the comment blocks that are in the example. These comments are
// guaranteed to be in order of appearance.
for _, cg := range fileComments {
if cg.Pos() < fn.Pos() {
continue
} else if cg.End() > fn.End() {
break
}
isOutput := outputRe.MatchString(cg.Text())
if isOutput {
numOutputs++
}
commentsInExample = append(commentsInExample, commentMetadata{
isOutput: isOutput,
pos: cg.Pos(),
})
}
// Change message based on whether there are multiple output comment blocks.
msg := "output comment block must be the last comment block"
if numOutputs > 1 {
msg = "there can only be one output comment block per example"
}
for i, cg := range commentsInExample {
// Check for output comments that are not the last comment in the example.
isLast := (i == len(commentsInExample)-1)
if cg.isOutput && !isLast {
pass.Report(
analysis.Diagnostic{
Pos: cg.pos,
Message: msg,
},
)
}
}
}
func checkExampleName(pass *analysis.Pass, fn *ast.FuncDecl) {
fnName := fn.Name.Name
if params := fn.Type.Params; len(params.List) != 0 {
pass.Reportf(fn.Pos(), "%s should be niladic", fnName)
}
if results := fn.Type.Results; results != nil && len(results.List) != 0 {
pass.Reportf(fn.Pos(), "%s should return nothing", fnName)
}
if tparams := fn.Type.TypeParams; tparams != nil && len(tparams.List) > 0 {
pass.Reportf(fn.Pos(), "%s should not have type params", fnName)
}
if fnName == "Example" {
// Nothing more to do.
return
}
var (
exName = strings.TrimPrefix(fnName, "Example")
elems = strings.SplitN(exName, "_", 3)
ident = elems[0]
objs = lookup(pass.Pkg, ident)
)
if ident != "" && len(objs) == 0 {
// Check ExampleFoo and ExampleBadFoo.
pass.Reportf(fn.Pos(), "%s refers to unknown identifier: %s", fnName, ident)
// Abort since obj is absent and no subsequent checks can be performed.
return
}
if len(elems) < 2 {
// Nothing more to do.
return
}
if ident == "" {
// Check Example_suffix and Example_BadSuffix.
if residual := strings.TrimPrefix(exName, "_"); !isExampleSuffix(residual) {
pass.Reportf(fn.Pos(), "%s has malformed example suffix: %s", fnName, residual)
}
return
}
mmbr := elems[1]
if !isExampleSuffix(mmbr) {
// Check ExampleFoo_Method and ExampleFoo_BadMethod.
found := false
// Check if Foo.Method exists in this package or its imports.
for _, obj := range objs {
if obj, _, _ := types.LookupFieldOrMethod(obj.Type(), true, obj.Pkg(), mmbr); obj != nil {
found = true
break
}
}
if !found {
pass.Reportf(fn.Pos(), "%s refers to unknown field or method: %s.%s", fnName, ident, mmbr)
}
}
if len(elems) == 3 && !isExampleSuffix(elems[2]) {
// Check ExampleFoo_Method_suffix and ExampleFoo_Method_Badsuffix.
pass.Reportf(fn.Pos(), "%s has malformed example suffix: %s", fnName, elems[2])
}
}
func checkTest(pass *analysis.Pass, fn *ast.FuncDecl, prefix string) {
// Want functions with 0 results and 1 parameter.
if fn.Type.Results != nil && len(fn.Type.Results.List) > 0 ||
fn.Type.Params == nil ||
len(fn.Type.Params.List) != 1 ||
len(fn.Type.Params.List[0].Names) > 1 {
return
}
// The param must look like a *testing.T or *testing.B.
if !isTestParam(fn.Type.Params.List[0].Type, prefix[:1]) {
return
}
if tparams := fn.Type.TypeParams; tparams != nil && len(tparams.List) > 0 {
// Note: cmd/go/internal/load also errors about TestXXX and BenchmarkXXX functions with type parameters.
// We have currently decided to also warn before compilation/package loading. This can help users in IDEs.
pass.ReportRangef(astutil.RangeOf(tparams.Opening, tparams.Closing),
"%s has type parameters: it will not be run by go test as a %sXXX function",
fn.Name.Name, prefix)
}
if !isTestSuffix(fn.Name.Name[len(prefix):]) {
pass.ReportRangef(fn.Name, "%s has malformed name: first letter after '%s' must not be lowercase", fn.Name.Name, prefix)
}
}
================================================
FILE: go/analysis/passes/tests/tests_test.go
================================================
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package tests_test
import (
"testing"
"golang.org/x/tools/go/analysis/analysistest"
"golang.org/x/tools/go/analysis/passes/tests"
)
func Test(t *testing.T) {
testdata := analysistest.TestData()
analysistest.Run(t, testdata, tests.Analyzer,
"a", // loads "a", "a [a.test]", and "a.test"
"b_x_test", // loads "b" and "b_x_test"
"divergent",
"typeparams",
)
}
================================================
FILE: go/analysis/passes/timeformat/doc.go
================================================
// Copyright 2023 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package timeformat defines an Analyzer that checks for the use
// of time.Format or time.Parse calls with a bad format.
//
// # Analyzer timeformat
//
// timeformat: check for calls of (time.Time).Format or time.Parse with 2006-02-01
//
// The timeformat checker looks for time formats with the 2006-02-01 (yyyy-dd-mm)
// format. Internationally, "yyyy-dd-mm" does not occur in common calendar date
// standards, and so it is more likely that 2006-01-02 (yyyy-mm-dd) was intended.
package timeformat
================================================
FILE: go/analysis/passes/timeformat/testdata/src/a/a.go
================================================
// Copyright 2022 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// This file contains tests for the timeformat checker.
package a
import (
"time"
"b"
)
func hasError() {
a, _ := time.Parse("2006-02-01 15:04:05", "2021-01-01 00:00:00") // want `2006-02-01 should be 2006-01-02`
a.Format(`2006-02-01`) // want `2006-02-01 should be 2006-01-02`
a.Format("2006-02-01 15:04:05") // want `2006-02-01 should be 2006-01-02`
const c = "2006-02-01"
a.Format(c) // want `2006-02-01 should be 2006-01-02`
}
func notHasError() {
a, _ := time.Parse("2006-01-02 15:04:05", "2021-01-01 00:00:00")
a.Format("2006-01-02")
const c = "2006-01-02"
a.Format(c)
v := "2006-02-01"
a.Format(v) // Allowed though variables.
m := map[string]string{
"y": "2006-02-01",
}
a.Format(m["y"])
s := []string{"2006-02-01"}
a.Format(s[0])
a.Format(badFormat())
o := b.Parse("2006-02-01 15:04:05", "2021-01-01 00:00:00")
o.Format("2006-02-01")
}
func badFormat() string {
return "2006-02-01"
}
================================================
FILE: go/analysis/passes/timeformat/testdata/src/a/a.go.golden
================================================
// Copyright 2022 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// This file contains tests for the timeformat checker.
package a
import (
"time"
"b"
)
func hasError() {
a, _ := time.Parse("2006-01-02 15:04:05", "2021-01-01 00:00:00") // want `2006-02-01 should be 2006-01-02`
a.Format(`2006-01-02`) // want `2006-02-01 should be 2006-01-02`
a.Format("2006-01-02 15:04:05") // want `2006-02-01 should be 2006-01-02`
const c = "2006-02-01"
a.Format(c) // want `2006-02-01 should be 2006-01-02`
}
func notHasError() {
a, _ := time.Parse("2006-01-02 15:04:05", "2021-01-01 00:00:00")
a.Format("2006-01-02")
const c = "2006-01-02"
a.Format(c)
v := "2006-02-01"
a.Format(v) // Allowed though variables.
m := map[string]string{
"y": "2006-02-01",
}
a.Format(m["y"])
s := []string{"2006-02-01"}
a.Format(s[0])
a.Format(badFormat())
o := b.Parse("2006-02-01 15:04:05", "2021-01-01 00:00:00")
o.Format("2006-02-01")
}
func badFormat() string {
return "2006-02-01"
}
================================================
FILE: go/analysis/passes/timeformat/testdata/src/b/b.go
================================================
package b
type B struct {
}
func Parse(string, string) B {
return B{}
}
func (b B) Format(string) {
}
================================================
FILE: go/analysis/passes/timeformat/timeformat.go
================================================
// Copyright 2022 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package timeformat defines an Analyzer that checks for the use
// of time.Format or time.Parse calls with a bad format.
package timeformat
import (
_ "embed"
"go/ast"
"go/constant"
"go/token"
"go/types"
"strings"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/ast/inspector"
"golang.org/x/tools/go/types/typeutil"
"golang.org/x/tools/internal/analysis/analyzerutil"
"golang.org/x/tools/internal/typesinternal"
)
const badFormat = "2006-02-01"
const goodFormat = "2006-01-02"
//go:embed doc.go
var doc string
var Analyzer = &analysis.Analyzer{
Name: "timeformat",
Doc: analyzerutil.MustExtractDoc(doc, "timeformat"),
URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/timeformat",
Requires: []*analysis.Analyzer{inspect.Analyzer},
Run: run,
}
func run(pass *analysis.Pass) (any, error) {
// Note: (time.Time).Format is a method and can be a typeutil.Callee
// without directly importing "time". So we cannot just skip this package
// when !analysis.Imports(pass.Pkg, "time").
// TODO(taking): Consider using a prepass to collect typeutil.Callees.
inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
nodeFilter := []ast.Node{
(*ast.CallExpr)(nil),
}
inspect.Preorder(nodeFilter, func(n ast.Node) {
call := n.(*ast.CallExpr)
obj := typeutil.Callee(pass.TypesInfo, call)
if !typesinternal.IsMethodNamed(obj, "time", "Time", "Format") &&
!typesinternal.IsFunctionNamed(obj, "time", "Parse") {
return
}
if len(call.Args) > 0 {
arg := call.Args[0]
badAt := badFormatAt(pass.TypesInfo, arg)
if badAt > -1 {
// Check if it's a literal string, otherwise we can't suggest a fix.
if _, ok := arg.(*ast.BasicLit); ok {
pos := int(arg.Pos()) + badAt + 1 // +1 to skip the " or `
end := pos + len(badFormat)
pass.Report(analysis.Diagnostic{
Pos: token.Pos(pos),
End: token.Pos(end),
Message: badFormat + " should be " + goodFormat,
SuggestedFixes: []analysis.SuggestedFix{{
Message: "Replace " + badFormat + " with " + goodFormat,
TextEdits: []analysis.TextEdit{{
Pos: token.Pos(pos),
End: token.Pos(end),
NewText: []byte(goodFormat),
}},
}},
})
} else {
pass.Reportf(arg.Pos(), badFormat+" should be "+goodFormat)
}
}
}
})
return nil, nil
}
// badFormatAt return the start of a bad format in e or -1 if no bad format is found.
func badFormatAt(info *types.Info, e ast.Expr) int {
tv, ok := info.Types[e]
if !ok { // no type info, assume good
return -1
}
t, ok := tv.Type.(*types.Basic) // sic, no unalias
if !ok || t.Info()&types.IsString == 0 {
return -1
}
if tv.Value == nil {
return -1
}
return strings.Index(constant.StringVal(tv.Value), badFormat)
}
================================================
FILE: go/analysis/passes/timeformat/timeformat_test.go
================================================
// Copyright 2022 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package timeformat_test
import (
"testing"
"golang.org/x/tools/go/analysis/analysistest"
"golang.org/x/tools/go/analysis/passes/timeformat"
)
func Test(t *testing.T) {
testdata := analysistest.TestData()
analysistest.RunWithSuggestedFixes(t, testdata, timeformat.Analyzer, "a")
}
================================================
FILE: go/analysis/passes/unmarshal/cmd/unmarshal/main.go
================================================
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// The unmarshal command runs the unmarshal analyzer.
package main
import (
"golang.org/x/tools/go/analysis/passes/unmarshal"
"golang.org/x/tools/go/analysis/singlechecker"
)
func main() { singlechecker.Main(unmarshal.Analyzer) }
================================================
FILE: go/analysis/passes/unmarshal/doc.go
================================================
// Copyright 2023 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// The unmarshal package defines an Analyzer that checks for passing
// non-pointer or non-interface types to unmarshal and decode functions.
//
// # Analyzer unmarshal
//
// unmarshal: report passing non-pointer or non-interface values to unmarshal
//
// The unmarshal analysis reports calls to functions such as json.Unmarshal
// in which the argument type is not a pointer or an interface.
package unmarshal
================================================
FILE: go/analysis/passes/unmarshal/testdata/src/a/a.go
================================================
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// This file contains tests for the unmarshal checker.
package testdata
import (
"encoding/asn1"
"encoding/gob"
"encoding/json"
"encoding/xml"
"io"
)
func _() {
type t struct {
a int
}
var v t
var r io.Reader
json.Unmarshal([]byte{}, v) // want "call of Unmarshal passes non-pointer as second argument"
json.Unmarshal([]byte{}, &v)
json.NewDecoder(r).Decode(v) // want "call of Decode passes non-pointer"
json.NewDecoder(r).Decode(&v)
gob.NewDecoder(r).Decode(v) // want "call of Decode passes non-pointer"
gob.NewDecoder(r).Decode(&v)
xml.Unmarshal([]byte{}, v) // want "call of Unmarshal passes non-pointer as second argument"
xml.Unmarshal([]byte{}, &v)
xml.NewDecoder(r).Decode(v) // want "call of Decode passes non-pointer"
xml.NewDecoder(r).Decode(&v)
asn1.Unmarshal([]byte{}, v) // want "call of Unmarshal passes non-pointer as second argument"
asn1.Unmarshal([]byte{}, &v)
var p *t
json.Unmarshal([]byte{}, p)
json.Unmarshal([]byte{}, *p) // want "call of Unmarshal passes non-pointer as second argument"
json.NewDecoder(r).Decode(p)
json.NewDecoder(r).Decode(*p) // want "call of Decode passes non-pointer"
gob.NewDecoder(r).Decode(p)
gob.NewDecoder(r).Decode(*p) // want "call of Decode passes non-pointer"
xml.Unmarshal([]byte{}, p)
xml.Unmarshal([]byte{}, *p) // want "call of Unmarshal passes non-pointer as second argument"
xml.NewDecoder(r).Decode(p)
xml.NewDecoder(r).Decode(*p) // want "call of Decode passes non-pointer"
asn1.Unmarshal([]byte{}, p)
asn1.Unmarshal([]byte{}, *p) // want "call of Unmarshal passes non-pointer as second argument"
var i interface{}
json.Unmarshal([]byte{}, i)
json.NewDecoder(r).Decode(i)
json.Unmarshal([]byte{}, nil) // want "call of Unmarshal passes non-pointer as second argument"
json.Unmarshal([]byte{}, []t{}) // want "call of Unmarshal passes non-pointer as second argument"
json.Unmarshal([]byte{}, map[string]int{}) // want "call of Unmarshal passes non-pointer as second argument"
json.NewDecoder(r).Decode(nil) // want "call of Decode passes non-pointer"
json.NewDecoder(r).Decode([]t{}) // want "call of Decode passes non-pointer"
json.NewDecoder(r).Decode(map[string]int{}) // want "call of Decode passes non-pointer"
json.Unmarshal(func() ([]byte, interface{}) { return []byte{}, v }())
}
================================================
FILE: go/analysis/passes/unmarshal/testdata/src/typeparams/typeparams.go
================================================
package typeparams
import (
"encoding/json"
"fmt"
)
func unmarshalT[T any](data []byte) T {
var x T
json.Unmarshal(data, x)
return x
}
func unmarshalT2[T any](data []byte, t T) {
json.Unmarshal(data, t)
}
func main() {
x := make(map[string]interface{})
unmarshalT2([]byte(`{"a":1}`), &x)
fmt.Println(x)
}
================================================
FILE: go/analysis/passes/unmarshal/unmarshal.go
================================================
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package unmarshal
import (
_ "embed"
"go/ast"
"go/types"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/ast/inspector"
"golang.org/x/tools/go/types/typeutil"
"golang.org/x/tools/internal/analysis/analyzerutil"
"golang.org/x/tools/internal/typesinternal"
)
//go:embed doc.go
var doc string
var Analyzer = &analysis.Analyzer{
Name: "unmarshal",
Doc: analyzerutil.MustExtractDoc(doc, "unmarshal"),
URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/unmarshal",
Requires: []*analysis.Analyzer{inspect.Analyzer},
Run: run,
}
func run(pass *analysis.Pass) (any, error) {
switch pass.Pkg.Path() {
case "encoding/gob", "encoding/json", "encoding/xml", "encoding/asn1":
// These packages know how to use their own APIs.
// Sometimes they are testing what happens to incorrect programs.
return nil, nil
}
// Note: (*"encoding/json".Decoder).Decode, (* "encoding/gob".Decoder).Decode
// and (* "encoding/xml".Decoder).Decode are methods and can be a typeutil.Callee
// without directly importing their packages. So we cannot just skip this package
// when !analysis.Imports(pass.Pkg, "encoding/...").
// TODO(taking): Consider using a prepass to collect typeutil.Callees.
inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
nodeFilter := []ast.Node{
(*ast.CallExpr)(nil),
}
inspect.Preorder(nodeFilter, func(n ast.Node) {
call := n.(*ast.CallExpr)
fn := typeutil.StaticCallee(pass.TypesInfo, call)
if fn == nil {
return // not a static call
}
// Classify the callee (without allocating memory).
argidx := -1
recv := fn.Signature().Recv()
if fn.Name() == "Unmarshal" && recv == nil {
// "encoding/json".Unmarshal
// "encoding/xml".Unmarshal
// "encoding/asn1".Unmarshal
switch fn.Pkg().Path() {
case "encoding/json", "encoding/xml", "encoding/asn1":
argidx = 1 // func([]byte, interface{})
}
} else if fn.Name() == "Decode" && recv != nil {
// (*"encoding/json".Decoder).Decode
// (* "encoding/gob".Decoder).Decode
// (* "encoding/xml".Decoder).Decode
_, named := typesinternal.ReceiverNamed(recv)
if tname := named.Obj(); tname.Name() == "Decoder" {
switch tname.Pkg().Path() {
case "encoding/json", "encoding/xml", "encoding/gob":
argidx = 0 // func(interface{})
}
}
}
if argidx < 0 {
return // not a function we are interested in
}
if len(call.Args) < argidx+1 {
return // not enough arguments, e.g. called with return values of another function
}
t := pass.TypesInfo.Types[call.Args[argidx]].Type
switch t.Underlying().(type) {
case *types.Pointer, *types.Interface, *types.TypeParam:
return
}
switch argidx {
case 0:
pass.Reportf(call.Lparen, "call of %s passes non-pointer", fn.Name())
case 1:
pass.Reportf(call.Lparen, "call of %s passes non-pointer as second argument", fn.Name())
}
})
return nil, nil
}
================================================
FILE: go/analysis/passes/unmarshal/unmarshal_test.go
================================================
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package unmarshal_test
import (
"testing"
"golang.org/x/tools/go/analysis/analysistest"
"golang.org/x/tools/go/analysis/passes/unmarshal"
)
func Test(t *testing.T) {
testdata := analysistest.TestData()
analysistest.Run(t, testdata, unmarshal.Analyzer, "a", "typeparams")
}
================================================
FILE: go/analysis/passes/unreachable/doc.go
================================================
// Copyright 2023 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package unreachable defines an Analyzer that checks for unreachable code.
//
// # Analyzer unreachable
//
// unreachable: check for unreachable code
//
// The unreachable analyzer finds statements that execution can never reach
// because they are preceded by a return statement, a call to panic, an
// infinite loop, or similar constructs.
package unreachable
================================================
FILE: go/analysis/passes/unreachable/testdata/src/a/a.go
================================================
// Copyright 2020 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package unreachable
// This file produces masses of errors from the type checker due to
// missing returns statements and other things.
type T int
var x interface{}
var c chan int
func external() int // ok
func _() int {
}
func _() int {
print(1)
}
func _() int {
print(1)
return 2
println() // want "unreachable code"
}
func _() int {
L:
print(1)
goto L
println() // want "unreachable code"
}
func _() int {
print(1)
panic(2)
println() // want "unreachable code"
}
// but only builtin panic
func _() int {
var panic = func(int) {}
print(1)
panic(2)
println() // ok
}
func _() int {
{
print(1)
return 2
println() // want "unreachable code"
}
println() // ok
}
func _() int {
{
print(1)
return 2
}
println() // want "unreachable code"
}
func _() int {
L:
{
print(1)
goto L
println() // want "unreachable code"
}
println() // ok
}
func _() int {
L:
{
print(1)
goto L
}
println() // want "unreachable code"
}
func _() int {
print(1)
{
panic(2)
}
}
func _() int {
print(1)
{
panic(2)
println() // want "unreachable code"
}
}
func _() int {
print(1)
{
panic(2)
}
println() // want "unreachable code"
}
func _() int {
print(1)
return 2
{ // want "unreachable code"
}
}
func _() int {
L:
print(1)
goto L
{ // want "unreachable code"
}
}
func _() int {
print(1)
panic(2)
{ // want "unreachable code"
}
}
func _() int {
{
print(1)
return 2
{ // want "unreachable code"
}
}
}
func _() int {
L:
{
print(1)
goto L
{ // want "unreachable code"
}
}
}
func _() int {
print(1)
{
panic(2)
{ // want "unreachable code"
}
}
}
func _() int {
{
print(1)
return 2
}
{ // want "unreachable code"
}
}
func _() int {
L:
{
print(1)
goto L
}
{ // want "unreachable code"
}
}
func _() int {
print(1)
{
panic(2)
}
{ // want "unreachable code"
}
}
func _() int {
print(1)
if x == nil {
panic(2)
} else {
panic(3)
}
println() // want "unreachable code"
}
func _() int {
L:
print(1)
if x == nil {
panic(2)
} else {
goto L
}
println() // want "unreachable code"
}
func _() int {
L:
print(1)
if x == nil {
panic(2)
} else if x == 1 {
return 0
} else if x != 2 {
panic(3)
} else {
goto L
}
println() // want "unreachable code"
}
// if-else chain missing final else is not okay, even if the
// conditions cover every possible case.
func _() int {
print(1)
if x == nil {
panic(2)
} else if x != nil {
panic(3)
}
println() // ok
}
func _() int {
print(1)
if x == nil {
panic(2)
}
println() // ok
}
func _() int {
L:
print(1)
if x == nil {
panic(2)
} else if x == 1 {
return 0
} else if x != 1 {
panic(3)
}
println() // ok
}
func _() int {
print(1)
for {
}
println() // want "unreachable code"
}
func _() int {
for {
for {
break
}
}
println() // want "unreachable code"
}
func _() int {
for {
for {
break
println() // want "unreachable code"
}
}
}
func _() int {
for {
for {
continue
println() // want "unreachable code"
}
}
}
func _() int {
for {
L:
for {
break L
}
}
println() // want "unreachable code"
}
func _() int {
print(1)
for {
break
}
println() // ok
}
func _() int {
for {
for {
}
break // want "unreachable code"
}
println() // ok
}
func _() int {
L:
for {
for {
break L
}
}
println() // ok
}
func _() int {
print(1)
for x == nil {
}
println() // ok
}
func _() int {
for x == nil {
for {
break
}
}
println() // ok
}
func _() int {
for x == nil {
L:
for {
break L
}
}
println() // ok
}
func _() int {
print(1)
for true {
}
println() // ok
}
func _() int {
for true {
for {
break
}
}
println() // ok
}
func _() int {
for true {
L:
for {
break L
}
}
println() // ok
}
func _() int {
print(1)
select {}
println() // want "unreachable code"
}
func _() int {
print(1)
select {
case <-c:
print(2)
panic("abc")
println() // want "unreachable code"
}
}
func _() int {
print(1)
select {
case <-c:
print(2)
panic("abc")
}
println() // want "unreachable code"
}
func _() int {
print(1)
select {
case <-c:
print(2)
for {
}
println() // want "unreachable code"
}
}
func _() int {
print(1)
select {
case <-c:
print(2)
for {
}
}
println() // want "unreachable code"
}
func _() int {
L:
print(1)
select {
case <-c:
print(2)
panic("abc")
println() // want "unreachable code"
case c <- 1:
print(2)
goto L
println() // want "unreachable code"
}
}
func _() int {
L:
print(1)
select {
case <-c:
print(2)
panic("abc")
case c <- 1:
print(2)
goto L
}
println() // want "unreachable code"
}
func _() int {
print(1)
select {
case <-c:
print(2)
panic("abc")
println() // want "unreachable code"
default:
select {}
println() // want "unreachable code"
}
}
func _() int {
print(1)
select {
case <-c:
print(2)
panic("abc")
default:
select {}
}
println() // want "unreachable code"
}
func _() int {
print(1)
select {
case <-c:
print(2)
}
println() // ok
}
func _() int {
L:
print(1)
select {
case <-c:
print(2)
panic("abc")
goto L // want "unreachable code"
case c <- 1:
print(2)
}
println() // ok
}
func _() int {
print(1)
select {
case <-c:
print(2)
panic("abc")
default:
print(2)
}
println() // ok
}
func _() int {
print(1)
select {
default:
break
}
println() // ok
}
func _() int {
print(1)
select {
case <-c:
print(2)
panic("abc")
break // want "unreachable code"
}
println() // ok
}
func _() int {
print(1)
L:
select {
case <-c:
print(2)
for {
break L
}
}
println() // ok
}
func _() int {
print(1)
L:
select {
case <-c:
print(2)
panic("abc")
case c <- 1:
print(2)
break L
}
println() // ok
}
func _() int {
print(1)
select {
case <-c:
print(1)
panic("abc")
default:
select {}
break // want "unreachable code"
}
println() // ok
}
func _() int {
print(1)
switch x {
case 1:
print(2)
panic(3)
println() // want "unreachable code"
default:
return 4
println() // want "unreachable code"
}
}
func _() int {
print(1)
switch x {
case 1:
print(2)
panic(3)
default:
return 4
}
println() // want "unreachable code"
}
func _() int {
print(1)
switch x {
default:
return 4
println() // want "unreachable code"
case 1:
print(2)
panic(3)
println() // want "unreachable code"
}
}
func _() int {
print(1)
switch x {
default:
return 4
case 1:
print(2)
panic(3)
}
println() // want "unreachable code"
}
func _() int {
print(1)
switch x {
case 1:
print(2)
fallthrough
default:
return 4
println() // want "unreachable code"
}
}
func _() int {
print(1)
switch x {
case 1:
print(2)
fallthrough
default:
return 4
}
println() // want "unreachable code"
}
func _() int {
print(1)
switch {
}
println() // ok
}
func _() int {
print(1)
switch x {
case 1:
print(2)
panic(3)
case 2:
return 4
}
println() // ok
}
func _() int {
print(1)
switch x {
case 2:
return 4
case 1:
print(2)
panic(3)
}
println() // ok
}
func _() int {
print(1)
switch x {
case 1:
print(2)
fallthrough
case 2:
return 4
}
println() // ok
}
func _() int {
print(1)
switch x {
case 1:
print(2)
panic(3)
}
println() // ok
}
func _() int {
print(1)
L:
switch x {
case 1:
print(2)
panic(3)
break L // want "unreachable code"
default:
return 4
}
println() // ok
}
func _() int {
print(1)
switch x {
default:
return 4
break // want "unreachable code"
case 1:
print(2)
panic(3)
}
println() // ok
}
func _() int {
print(1)
L:
switch x {
case 1:
print(2)
for {
break L
}
default:
return 4
}
println() // ok
}
func _() int {
print(1)
switch x.(type) {
case int:
print(2)
panic(3)
println() // want "unreachable code"
default:
return 4
println() // want "unreachable code"
}
}
func _() int {
print(1)
switch x.(type) {
case int:
print(2)
panic(3)
default:
return 4
}
println() // want "unreachable code"
}
func _() int {
print(1)
switch x.(type) {
default:
return 4
println() // want "unreachable code"
case int:
print(2)
panic(3)
println() // want "unreachable code"
}
}
func _() int {
print(1)
switch x.(type) {
default:
return 4
case int:
print(2)
panic(3)
}
println() // want "unreachable code"
}
func _() int {
print(1)
switch x.(type) {
case int:
print(2)
fallthrough
default:
return 4
println() // want "unreachable code"
}
}
func _() int {
print(1)
switch x.(type) {
case int:
print(2)
fallthrough
default:
return 4
}
println() // want "unreachable code"
}
func _() int {
print(1)
switch {
}
println() // ok
}
func _() int {
print(1)
switch x.(type) {
case int:
print(2)
panic(3)
case float64:
return 4
}
println() // ok
}
func _() int {
print(1)
switch x.(type) {
case float64:
return 4
case int:
print(2)
panic(3)
}
println() // ok
}
func _() int {
print(1)
switch x.(type) {
case int:
print(2)
fallthrough
case float64:
return 4
}
println() // ok
}
func _() int {
print(1)
switch x.(type) {
case int:
print(2)
panic(3)
}
println() // ok
}
func _() int {
print(1)
L:
switch x.(type) {
case int:
print(2)
panic(3)
break L // want "unreachable code"
default:
return 4
}
println() // ok
}
func _() int {
print(1)
switch x.(type) {
default:
return 4
break // want "unreachable code"
case int:
print(2)
panic(3)
}
println() // ok
}
func _() int {
print(1)
L:
switch x.(type) {
case int:
print(2)
for {
break L
}
default:
return 4
}
println() // ok
}
// again, but without the leading print(1).
// testing that everything works when the terminating statement is first.
func _() int {
println() // ok
}
func _() int {
return 2
println() // want "unreachable code"
}
func _() int {
L:
goto L
println() // want "unreachable code"
}
func _() int {
panic(2)
println() // want "unreachable code"
}
// but only builtin panic
func _() int {
var panic = func(int) {}
panic(2)
println() // ok
}
func _() int {
{
return 2
println() // want "unreachable code"
}
}
func _() int {
{
return 2
}
println() // want "unreachable code"
}
func _() int {
L:
{
goto L
println() // want "unreachable code"
}
}
func _() int {
L:
{
goto L
}
println() // want "unreachable code"
}
func _() int {
{
panic(2)
println() // want "unreachable code"
}
}
func _() int {
{
panic(2)
}
println() // want "unreachable code"
}
func _() int {
return 2
{ // want "unreachable code"
}
println() // ok
}
func _() int {
L:
goto L
{ // want "unreachable code"
}
println() // ok
}
func _() int {
panic(2)
{ // want "unreachable code"
}
println() // ok
}
func _() int {
{
return 2
{ // want "unreachable code"
}
}
println() // ok
}
func _() int {
L:
{
goto L
{ // want "unreachable code"
}
}
println() // ok
}
func _() int {
{
panic(2)
{ // want "unreachable code"
}
}
println() // ok
}
func _() int {
{
return 2
}
{ // want "unreachable code"
}
println() // ok
}
func _() int {
L:
{
goto L
}
{ // want "unreachable code"
}
println() // ok
}
func _() int {
{
panic(2)
}
{ // want "unreachable code"
}
println() // ok
}
// again, with func literals
var _ = func() int {
}
var _ = func() int {
print(1)
}
var _ = func() int {
print(1)
return 2
println() // want "unreachable code"
}
var _ = func() int {
L:
print(1)
goto L
println() // want "unreachable code"
}
var _ = func() int {
print(1)
panic(2)
println() // want "unreachable code"
}
// but only builtin panic
var _ = func() int {
var panic = func(int) {}
print(1)
panic(2)
println() // ok
}
var _ = func() int {
{
print(1)
return 2
println() // want "unreachable code"
}
println() // ok
}
var _ = func() int {
{
print(1)
return 2
}
println() // want "unreachable code"
}
var _ = func() int {
L:
{
print(1)
goto L
println() // want "unreachable code"
}
println() // ok
}
var _ = func() int {
L:
{
print(1)
goto L
}
println() // want "unreachable code"
}
var _ = func() int {
print(1)
{
panic(2)
}
}
var _ = func() int {
print(1)
{
panic(2)
println() // want "unreachable code"
}
}
var _ = func() int {
print(1)
{
panic(2)
}
println() // want "unreachable code"
}
var _ = func() int {
print(1)
return 2
{ // want "unreachable code"
}
}
var _ = func() int {
L:
print(1)
goto L
{ // want "unreachable code"
}
}
var _ = func() int {
print(1)
panic(2)
{ // want "unreachable code"
}
}
var _ = func() int {
{
print(1)
return 2
{ // want "unreachable code"
}
}
}
var _ = func() int {
L:
{
print(1)
goto L
{ // want "unreachable code"
}
}
}
var _ = func() int {
print(1)
{
panic(2)
{ // want "unreachable code"
}
}
}
var _ = func() int {
{
print(1)
return 2
}
{ // want "unreachable code"
}
}
var _ = func() int {
L:
{
print(1)
goto L
}
{ // want "unreachable code"
}
}
var _ = func() int {
print(1)
{
panic(2)
}
{ // want "unreachable code"
}
}
var _ = func() int {
print(1)
if x == nil {
panic(2)
} else {
panic(3)
}
println() // want "unreachable code"
}
var _ = func() int {
L:
print(1)
if x == nil {
panic(2)
} else {
goto L
}
println() // want "unreachable code"
}
var _ = func() int {
L:
print(1)
if x == nil {
panic(2)
} else if x == 1 {
return 0
} else if x != 2 {
panic(3)
} else {
goto L
}
println() // want "unreachable code"
}
// if-else chain missing final else is not okay, even if the
// conditions cover every possible case.
var _ = func() int {
print(1)
if x == nil {
panic(2)
} else if x != nil {
panic(3)
}
println() // ok
}
var _ = func() int {
print(1)
if x == nil {
panic(2)
}
println() // ok
}
var _ = func() int {
L:
print(1)
if x == nil {
panic(2)
} else if x == 1 {
return 0
} else if x != 1 {
panic(3)
}
println() // ok
}
var _ = func() int {
print(1)
for {
}
println() // want "unreachable code"
}
var _ = func() int {
for {
for {
break
}
}
println() // want "unreachable code"
}
var _ = func() int {
for {
for {
break
println() // want "unreachable code"
}
}
}
var _ = func() int {
for {
for {
continue
println() // want "unreachable code"
}
}
}
var _ = func() int {
for {
L:
for {
break L
}
}
println() // want "unreachable code"
}
var _ = func() int {
print(1)
for {
break
}
println() // ok
}
var _ = func() int {
for {
for {
}
break // want "unreachable code"
}
println() // ok
}
var _ = func() int {
L:
for {
for {
break L
}
}
println() // ok
}
var _ = func() int {
print(1)
for x == nil {
}
println() // ok
}
var _ = func() int {
for x == nil {
for {
break
}
}
println() // ok
}
var _ = func() int {
for x == nil {
L:
for {
break L
}
}
println() // ok
}
var _ = func() int {
print(1)
for true {
}
println() // ok
}
var _ = func() int {
for true {
for {
break
}
}
println() // ok
}
var _ = func() int {
for true {
L:
for {
break L
}
}
println() // ok
}
var _ = func() int {
print(1)
select {}
println() // want "unreachable code"
}
var _ = func() int {
print(1)
select {
case <-c:
print(2)
panic("abc")
println() // want "unreachable code"
}
}
var _ = func() int {
print(1)
select {
case <-c:
print(2)
panic("abc")
}
println() // want "unreachable code"
}
var _ = func() int {
print(1)
select {
case <-c:
print(2)
for {
}
println() // want "unreachable code"
}
}
var _ = func() int {
print(1)
select {
case <-c:
print(2)
for {
}
}
println() // want "unreachable code"
}
var _ = func() int {
L:
print(1)
select {
case <-c:
print(2)
panic("abc")
println() // want "unreachable code"
case c <- 1:
print(2)
goto L
println() // want "unreachable code"
}
}
var _ = func() int {
L:
print(1)
select {
case <-c:
print(2)
panic("abc")
case c <- 1:
print(2)
goto L
}
println() // want "unreachable code"
}
var _ = func() int {
print(1)
select {
case <-c:
print(2)
panic("abc")
println() // want "unreachable code"
default:
select {}
println() // want "unreachable code"
}
}
var _ = func() int {
print(1)
select {
case <-c:
print(2)
panic("abc")
default:
select {}
}
println() // want "unreachable code"
}
var _ = func() int {
print(1)
select {
case <-c:
print(2)
}
println() // ok
}
var _ = func() int {
L:
print(1)
select {
case <-c:
print(2)
panic("abc")
goto L // want "unreachable code"
case c <- 1:
print(2)
}
println() // ok
}
var _ = func() int {
print(1)
select {
case <-c:
print(2)
panic("abc")
default:
print(2)
}
println() // ok
}
var _ = func() int {
print(1)
select {
default:
break
}
println() // ok
}
var _ = func() int {
print(1)
select {
case <-c:
print(2)
panic("abc")
break // want "unreachable code"
}
println() // ok
}
var _ = func() int {
print(1)
L:
select {
case <-c:
print(2)
for {
break L
}
}
println() // ok
}
var _ = func() int {
print(1)
L:
select {
case <-c:
print(2)
panic("abc")
case c <- 1:
print(2)
break L
}
println() // ok
}
var _ = func() int {
print(1)
select {
case <-c:
print(1)
panic("abc")
default:
select {}
break // want "unreachable code"
}
println() // ok
}
var _ = func() int {
print(1)
switch x {
case 1:
print(2)
panic(3)
println() // want "unreachable code"
default:
return 4
println() // want "unreachable code"
}
}
var _ = func() int {
print(1)
switch x {
case 1:
print(2)
panic(3)
default:
return 4
}
println() // want "unreachable code"
}
var _ = func() int {
print(1)
switch x {
default:
return 4
println() // want "unreachable code"
case 1:
print(2)
panic(3)
println() // want "unreachable code"
}
}
var _ = func() int {
print(1)
switch x {
default:
return 4
case 1:
print(2)
panic(3)
}
println() // want "unreachable code"
}
var _ = func() int {
print(1)
switch x {
case 1:
print(2)
fallthrough
default:
return 4
println() // want "unreachable code"
}
}
var _ = func() int {
print(1)
switch x {
case 1:
print(2)
fallthrough
default:
return 4
}
println() // want "unreachable code"
}
var _ = func() int {
print(1)
switch {
}
println() // ok
}
var _ = func() int {
print(1)
switch x {
case 1:
print(2)
panic(3)
case 2:
return 4
}
println() // ok
}
var _ = func() int {
print(1)
switch x {
case 2:
return 4
case 1:
print(2)
panic(3)
}
println() // ok
}
var _ = func() int {
print(1)
switch x {
case 1:
print(2)
fallthrough
case 2:
return 4
}
println() // ok
}
var _ = func() int {
print(1)
switch x {
case 1:
print(2)
panic(3)
}
println() // ok
}
var _ = func() int {
print(1)
L:
switch x {
case 1:
print(2)
panic(3)
break L // want "unreachable code"
default:
return 4
}
println() // ok
}
var _ = func() int {
print(1)
switch x {
default:
return 4
break // want "unreachable code"
case 1:
print(2)
panic(3)
}
println() // ok
}
var _ = func() int {
print(1)
L:
switch x {
case 1:
print(2)
for {
break L
}
default:
return 4
}
println() // ok
}
var _ = func() int {
print(1)
switch x.(type) {
case int:
print(2)
panic(3)
println() // want "unreachable code"
default:
return 4
println() // want "unreachable code"
}
}
var _ = func() int {
print(1)
switch x.(type) {
case int:
print(2)
panic(3)
default:
return 4
}
println() // want "unreachable code"
}
var _ = func() int {
print(1)
switch x.(type) {
default:
return 4
println() // want "unreachable code"
case int:
print(2)
panic(3)
println() // want "unreachable code"
}
}
var _ = func() int {
print(1)
switch x.(type) {
default:
return 4
case int:
print(2)
panic(3)
}
println() // want "unreachable code"
}
var _ = func() int {
print(1)
switch x.(type) {
case int:
print(2)
fallthrough
default:
return 4
println() // want "unreachable code"
}
}
var _ = func() int {
print(1)
switch x.(type) {
case int:
print(2)
fallthrough
default:
return 4
}
println() // want "unreachable code"
}
var _ = func() int {
print(1)
switch {
}
println() // ok
}
var _ = func() int {
print(1)
switch x.(type) {
case int:
print(2)
panic(3)
case float64:
return 4
}
println() // ok
}
var _ = func() int {
print(1)
switch x.(type) {
case float64:
return 4
case int:
print(2)
panic(3)
}
println() // ok
}
var _ = func() int {
print(1)
switch x.(type) {
case int:
print(2)
fallthrough
case float64:
return 4
}
println() // ok
}
var _ = func() int {
print(1)
switch x.(type) {
case int:
print(2)
panic(3)
}
println() // ok
}
var _ = func() int {
print(1)
L:
switch x.(type) {
case int:
print(2)
panic(3)
break L // want "unreachable code"
default:
return 4
}
println() // ok
}
var _ = func() int {
print(1)
switch x.(type) {
default:
return 4
break // want "unreachable code"
case int:
print(2)
panic(3)
}
println() // ok
}
var _ = func() int {
print(1)
L:
switch x.(type) {
case int:
print(2)
for {
break L
}
default:
return 4
}
println() // ok
}
// again, but without the leading print(1).
// testing that everything works when the terminating statement is first.
var _ = func() int {
println() // ok
}
var _ = func() int {
return 2
println() // want "unreachable code"
}
var _ = func() int {
L:
goto L
println() // want "unreachable code"
}
var _ = func() int {
panic(2)
println() // want "unreachable code"
}
// but only builtin panic
var _ = func() int {
var panic = func(int) {}
panic(2)
println() // ok
}
var _ = func() int {
{
return 2
println() // want "unreachable code"
}
}
var _ = func() int {
{
return 2
}
println() // want "unreachable code"
}
var _ = func() int {
L:
{
goto L
println() // want "unreachable code"
}
}
var _ = func() int {
L:
{
goto L
}
println() // want "unreachable code"
}
var _ = func() int {
{
panic(2)
println() // want "unreachable code"
}
}
var _ = func() int {
{
panic(2)
}
println() // want "unreachable code"
}
var _ = func() int {
return 2
{ // want "unreachable code"
}
println() // ok
}
var _ = func() int {
L:
goto L
{ // want "unreachable code"
}
println() // ok
}
var _ = func() int {
panic(2)
{ // want "unreachable code"
}
println() // ok
}
var _ = func() int {
{
return 2
{ // want "unreachable code"
}
}
println() // ok
}
var _ = func() int {
L:
{
goto L
{ // want "unreachable code"
}
}
println() // ok
}
var _ = func() int {
{
panic(2)
{ // want "unreachable code"
}
}
println() // ok
}
var _ = func() int {
{
return 2
}
{ // want "unreachable code"
}
println() // ok
}
var _ = func() int {
L:
{
goto L
}
{ // want "unreachable code"
}
println() // ok
}
var _ = func() int {
{
panic(2)
}
{ // want "unreachable code"
}
println() // ok
}
func _() int {
// Empty switch tag with non-bool case value used to panic.
switch {
case 1:
println()
}
println()
}
================================================
FILE: go/analysis/passes/unreachable/testdata/src/a/a.go.golden
================================================
// Copyright 2020 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package unreachable
// This file produces masses of errors from the type checker due to
// missing returns statements and other things.
type T int
var x interface{}
var c chan int
func external() int // ok
func _() int {
}
func _() int {
print(1)
}
func _() int {
print(1)
return 2
}
func _() int {
L:
print(1)
goto L
}
func _() int {
print(1)
panic(2)
}
// but only builtin panic
func _() int {
var panic = func(int) {}
print(1)
panic(2)
println() // ok
}
func _() int {
{
print(1)
return 2
}
println() // ok
}
func _() int {
{
print(1)
return 2
}
}
func _() int {
L:
{
print(1)
goto L
}
println() // ok
}
func _() int {
L:
{
print(1)
goto L
}
}
func _() int {
print(1)
{
panic(2)
}
}
func _() int {
print(1)
{
panic(2)
}
}
func _() int {
print(1)
{
panic(2)
}
}
func _() int {
print(1)
return 2
}
func _() int {
L:
print(1)
goto L
}
func _() int {
print(1)
panic(2)
}
func _() int {
{
print(1)
return 2
}
}
func _() int {
L:
{
print(1)
goto L
}
}
func _() int {
print(1)
{
panic(2)
}
}
func _() int {
{
print(1)
return 2
}
}
func _() int {
L:
{
print(1)
goto L
}
}
func _() int {
print(1)
{
panic(2)
}
}
func _() int {
print(1)
if x == nil {
panic(2)
} else {
panic(3)
}
}
func _() int {
L:
print(1)
if x == nil {
panic(2)
} else {
goto L
}
}
func _() int {
L:
print(1)
if x == nil {
panic(2)
} else if x == 1 {
return 0
} else if x != 2 {
panic(3)
} else {
goto L
}
}
// if-else chain missing final else is not okay, even if the
// conditions cover every possible case.
func _() int {
print(1)
if x == nil {
panic(2)
} else if x != nil {
panic(3)
}
println() // ok
}
func _() int {
print(1)
if x == nil {
panic(2)
}
println() // ok
}
func _() int {
L:
print(1)
if x == nil {
panic(2)
} else if x == 1 {
return 0
} else if x != 1 {
panic(3)
}
println() // ok
}
func _() int {
print(1)
for {
}
}
func _() int {
for {
for {
break
}
}
}
func _() int {
for {
for {
break
}
}
}
func _() int {
for {
for {
continue
}
}
}
func _() int {
for {
L:
for {
break L
}
}
}
func _() int {
print(1)
for {
break
}
println() // ok
}
func _() int {
for {
for {
}
}
println() // ok
}
func _() int {
L:
for {
for {
break L
}
}
println() // ok
}
func _() int {
print(1)
for x == nil {
}
println() // ok
}
func _() int {
for x == nil {
for {
break
}
}
println() // ok
}
func _() int {
for x == nil {
L:
for {
break L
}
}
println() // ok
}
func _() int {
print(1)
for true {
}
println() // ok
}
func _() int {
for true {
for {
break
}
}
println() // ok
}
func _() int {
for true {
L:
for {
break L
}
}
println() // ok
}
func _() int {
print(1)
select {}
}
func _() int {
print(1)
select {
case <-c:
print(2)
panic("abc")
}
}
func _() int {
print(1)
select {
case <-c:
print(2)
panic("abc")
}
}
func _() int {
print(1)
select {
case <-c:
print(2)
for {
}
}
}
func _() int {
print(1)
select {
case <-c:
print(2)
for {
}
}
}
func _() int {
L:
print(1)
select {
case <-c:
print(2)
panic("abc")
case c <- 1:
print(2)
goto L
}
}
func _() int {
L:
print(1)
select {
case <-c:
print(2)
panic("abc")
case c <- 1:
print(2)
goto L
}
}
func _() int {
print(1)
select {
case <-c:
print(2)
panic("abc")
default:
select {}
}
}
func _() int {
print(1)
select {
case <-c:
print(2)
panic("abc")
default:
select {}
}
}
func _() int {
print(1)
select {
case <-c:
print(2)
}
println() // ok
}
func _() int {
L:
print(1)
select {
case <-c:
print(2)
panic("abc")
case c <- 1:
print(2)
}
println() // ok
}
func _() int {
print(1)
select {
case <-c:
print(2)
panic("abc")
default:
print(2)
}
println() // ok
}
func _() int {
print(1)
select {
default:
break
}
println() // ok
}
func _() int {
print(1)
select {
case <-c:
print(2)
panic("abc")
}
println() // ok
}
func _() int {
print(1)
L:
select {
case <-c:
print(2)
for {
break L
}
}
println() // ok
}
func _() int {
print(1)
L:
select {
case <-c:
print(2)
panic("abc")
case c <- 1:
print(2)
break L
}
println() // ok
}
func _() int {
print(1)
select {
case <-c:
print(1)
panic("abc")
default:
select {}
}
println() // ok
}
func _() int {
print(1)
switch x {
case 1:
print(2)
panic(3)
default:
return 4
}
}
func _() int {
print(1)
switch x {
case 1:
print(2)
panic(3)
default:
return 4
}
}
func _() int {
print(1)
switch x {
default:
return 4
case 1:
print(2)
panic(3)
}
}
func _() int {
print(1)
switch x {
default:
return 4
case 1:
print(2)
panic(3)
}
}
func _() int {
print(1)
switch x {
case 1:
print(2)
fallthrough
default:
return 4
}
}
func _() int {
print(1)
switch x {
case 1:
print(2)
fallthrough
default:
return 4
}
}
func _() int {
print(1)
switch {
}
println() // ok
}
func _() int {
print(1)
switch x {
case 1:
print(2)
panic(3)
case 2:
return 4
}
println() // ok
}
func _() int {
print(1)
switch x {
case 2:
return 4
case 1:
print(2)
panic(3)
}
println() // ok
}
func _() int {
print(1)
switch x {
case 1:
print(2)
fallthrough
case 2:
return 4
}
println() // ok
}
func _() int {
print(1)
switch x {
case 1:
print(2)
panic(3)
}
println() // ok
}
func _() int {
print(1)
L:
switch x {
case 1:
print(2)
panic(3)
default:
return 4
}
println() // ok
}
func _() int {
print(1)
switch x {
default:
return 4
case 1:
print(2)
panic(3)
}
println() // ok
}
func _() int {
print(1)
L:
switch x {
case 1:
print(2)
for {
break L
}
default:
return 4
}
println() // ok
}
func _() int {
print(1)
switch x.(type) {
case int:
print(2)
panic(3)
default:
return 4
}
}
func _() int {
print(1)
switch x.(type) {
case int:
print(2)
panic(3)
default:
return 4
}
}
func _() int {
print(1)
switch x.(type) {
default:
return 4
case int:
print(2)
panic(3)
}
}
func _() int {
print(1)
switch x.(type) {
default:
return 4
case int:
print(2)
panic(3)
}
}
func _() int {
print(1)
switch x.(type) {
case int:
print(2)
fallthrough
default:
return 4
}
}
func _() int {
print(1)
switch x.(type) {
case int:
print(2)
fallthrough
default:
return 4
}
}
func _() int {
print(1)
switch {
}
println() // ok
}
func _() int {
print(1)
switch x.(type) {
case int:
print(2)
panic(3)
case float64:
return 4
}
println() // ok
}
func _() int {
print(1)
switch x.(type) {
case float64:
return 4
case int:
print(2)
panic(3)
}
println() // ok
}
func _() int {
print(1)
switch x.(type) {
case int:
print(2)
fallthrough
case float64:
return 4
}
println() // ok
}
func _() int {
print(1)
switch x.(type) {
case int:
print(2)
panic(3)
}
println() // ok
}
func _() int {
print(1)
L:
switch x.(type) {
case int:
print(2)
panic(3)
default:
return 4
}
println() // ok
}
func _() int {
print(1)
switch x.(type) {
default:
return 4
case int:
print(2)
panic(3)
}
println() // ok
}
func _() int {
print(1)
L:
switch x.(type) {
case int:
print(2)
for {
break L
}
default:
return 4
}
println() // ok
}
// again, but without the leading print(1).
// testing that everything works when the terminating statement is first.
func _() int {
println() // ok
}
func _() int {
return 2
}
func _() int {
L:
goto L
}
func _() int {
panic(2)
}
// but only builtin panic
func _() int {
var panic = func(int) {}
panic(2)
println() // ok
}
func _() int {
{
return 2
}
}
func _() int {
{
return 2
}
}
func _() int {
L:
{
goto L
}
}
func _() int {
L:
{
goto L
}
}
func _() int {
{
panic(2)
}
}
func _() int {
{
panic(2)
}
}
func _() int {
return 2
println() // ok
}
func _() int {
L:
goto L
println() // ok
}
func _() int {
panic(2)
println() // ok
}
func _() int {
{
return 2
}
println() // ok
}
func _() int {
L:
{
goto L
}
println() // ok
}
func _() int {
{
panic(2)
}
println() // ok
}
func _() int {
{
return 2
}
println() // ok
}
func _() int {
L:
{
goto L
}
println() // ok
}
func _() int {
{
panic(2)
}
println() // ok
}
// again, with func literals
var _ = func() int {
}
var _ = func() int {
print(1)
}
var _ = func() int {
print(1)
return 2
}
var _ = func() int {
L:
print(1)
goto L
}
var _ = func() int {
print(1)
panic(2)
}
// but only builtin panic
var _ = func() int {
var panic = func(int) {}
print(1)
panic(2)
println() // ok
}
var _ = func() int {
{
print(1)
return 2
}
println() // ok
}
var _ = func() int {
{
print(1)
return 2
}
}
var _ = func() int {
L:
{
print(1)
goto L
}
println() // ok
}
var _ = func() int {
L:
{
print(1)
goto L
}
}
var _ = func() int {
print(1)
{
panic(2)
}
}
var _ = func() int {
print(1)
{
panic(2)
}
}
var _ = func() int {
print(1)
{
panic(2)
}
}
var _ = func() int {
print(1)
return 2
}
var _ = func() int {
L:
print(1)
goto L
}
var _ = func() int {
print(1)
panic(2)
}
var _ = func() int {
{
print(1)
return 2
}
}
var _ = func() int {
L:
{
print(1)
goto L
}
}
var _ = func() int {
print(1)
{
panic(2)
}
}
var _ = func() int {
{
print(1)
return 2
}
}
var _ = func() int {
L:
{
print(1)
goto L
}
}
var _ = func() int {
print(1)
{
panic(2)
}
}
var _ = func() int {
print(1)
if x == nil {
panic(2)
} else {
panic(3)
}
}
var _ = func() int {
L:
print(1)
if x == nil {
panic(2)
} else {
goto L
}
}
var _ = func() int {
L:
print(1)
if x == nil {
panic(2)
} else if x == 1 {
return 0
} else if x != 2 {
panic(3)
} else {
goto L
}
}
// if-else chain missing final else is not okay, even if the
// conditions cover every possible case.
var _ = func() int {
print(1)
if x == nil {
panic(2)
} else if x != nil {
panic(3)
}
println() // ok
}
var _ = func() int {
print(1)
if x == nil {
panic(2)
}
println() // ok
}
var _ = func() int {
L:
print(1)
if x == nil {
panic(2)
} else if x == 1 {
return 0
} else if x != 1 {
panic(3)
}
println() // ok
}
var _ = func() int {
print(1)
for {
}
}
var _ = func() int {
for {
for {
break
}
}
}
var _ = func() int {
for {
for {
break
}
}
}
var _ = func() int {
for {
for {
continue
}
}
}
var _ = func() int {
for {
L:
for {
break L
}
}
}
var _ = func() int {
print(1)
for {
break
}
println() // ok
}
var _ = func() int {
for {
for {
}
}
println() // ok
}
var _ = func() int {
L:
for {
for {
break L
}
}
println() // ok
}
var _ = func() int {
print(1)
for x == nil {
}
println() // ok
}
var _ = func() int {
for x == nil {
for {
break
}
}
println() // ok
}
var _ = func() int {
for x == nil {
L:
for {
break L
}
}
println() // ok
}
var _ = func() int {
print(1)
for true {
}
println() // ok
}
var _ = func() int {
for true {
for {
break
}
}
println() // ok
}
var _ = func() int {
for true {
L:
for {
break L
}
}
println() // ok
}
var _ = func() int {
print(1)
select {}
}
var _ = func() int {
print(1)
select {
case <-c:
print(2)
panic("abc")
}
}
var _ = func() int {
print(1)
select {
case <-c:
print(2)
panic("abc")
}
}
var _ = func() int {
print(1)
select {
case <-c:
print(2)
for {
}
}
}
var _ = func() int {
print(1)
select {
case <-c:
print(2)
for {
}
}
}
var _ = func() int {
L:
print(1)
select {
case <-c:
print(2)
panic("abc")
case c <- 1:
print(2)
goto L
}
}
var _ = func() int {
L:
print(1)
select {
case <-c:
print(2)
panic("abc")
case c <- 1:
print(2)
goto L
}
}
var _ = func() int {
print(1)
select {
case <-c:
print(2)
panic("abc")
default:
select {}
}
}
var _ = func() int {
print(1)
select {
case <-c:
print(2)
panic("abc")
default:
select {}
}
}
var _ = func() int {
print(1)
select {
case <-c:
print(2)
}
println() // ok
}
var _ = func() int {
L:
print(1)
select {
case <-c:
print(2)
panic("abc")
case c <- 1:
print(2)
}
println() // ok
}
var _ = func() int {
print(1)
select {
case <-c:
print(2)
panic("abc")
default:
print(2)
}
println() // ok
}
var _ = func() int {
print(1)
select {
default:
break
}
println() // ok
}
var _ = func() int {
print(1)
select {
case <-c:
print(2)
panic("abc")
}
println() // ok
}
var _ = func() int {
print(1)
L:
select {
case <-c:
print(2)
for {
break L
}
}
println() // ok
}
var _ = func() int {
print(1)
L:
select {
case <-c:
print(2)
panic("abc")
case c <- 1:
print(2)
break L
}
println() // ok
}
var _ = func() int {
print(1)
select {
case <-c:
print(1)
panic("abc")
default:
select {}
}
println() // ok
}
var _ = func() int {
print(1)
switch x {
case 1:
print(2)
panic(3)
default:
return 4
}
}
var _ = func() int {
print(1)
switch x {
case 1:
print(2)
panic(3)
default:
return 4
}
}
var _ = func() int {
print(1)
switch x {
default:
return 4
case 1:
print(2)
panic(3)
}
}
var _ = func() int {
print(1)
switch x {
default:
return 4
case 1:
print(2)
panic(3)
}
}
var _ = func() int {
print(1)
switch x {
case 1:
print(2)
fallthrough
default:
return 4
}
}
var _ = func() int {
print(1)
switch x {
case 1:
print(2)
fallthrough
default:
return 4
}
}
var _ = func() int {
print(1)
switch {
}
println() // ok
}
var _ = func() int {
print(1)
switch x {
case 1:
print(2)
panic(3)
case 2:
return 4
}
println() // ok
}
var _ = func() int {
print(1)
switch x {
case 2:
return 4
case 1:
print(2)
panic(3)
}
println() // ok
}
var _ = func() int {
print(1)
switch x {
case 1:
print(2)
fallthrough
case 2:
return 4
}
println() // ok
}
var _ = func() int {
print(1)
switch x {
case 1:
print(2)
panic(3)
}
println() // ok
}
var _ = func() int {
print(1)
L:
switch x {
case 1:
print(2)
panic(3)
default:
return 4
}
println() // ok
}
var _ = func() int {
print(1)
switch x {
default:
return 4
case 1:
print(2)
panic(3)
}
println() // ok
}
var _ = func() int {
print(1)
L:
switch x {
case 1:
print(2)
for {
break L
}
default:
return 4
}
println() // ok
}
var _ = func() int {
print(1)
switch x.(type) {
case int:
print(2)
panic(3)
default:
return 4
}
}
var _ = func() int {
print(1)
switch x.(type) {
case int:
print(2)
panic(3)
default:
return 4
}
}
var _ = func() int {
print(1)
switch x.(type) {
default:
return 4
case int:
print(2)
panic(3)
}
}
var _ = func() int {
print(1)
switch x.(type) {
default:
return 4
case int:
print(2)
panic(3)
}
}
var _ = func() int {
print(1)
switch x.(type) {
case int:
print(2)
fallthrough
default:
return 4
}
}
var _ = func() int {
print(1)
switch x.(type) {
case int:
print(2)
fallthrough
default:
return 4
}
}
var _ = func() int {
print(1)
switch {
}
println() // ok
}
var _ = func() int {
print(1)
switch x.(type) {
case int:
print(2)
panic(3)
case float64:
return 4
}
println() // ok
}
var _ = func() int {
print(1)
switch x.(type) {
case float64:
return 4
case int:
print(2)
panic(3)
}
println() // ok
}
var _ = func() int {
print(1)
switch x.(type) {
case int:
print(2)
fallthrough
case float64:
return 4
}
println() // ok
}
var _ = func() int {
print(1)
switch x.(type) {
case int:
print(2)
panic(3)
}
println() // ok
}
var _ = func() int {
print(1)
L:
switch x.(type) {
case int:
print(2)
panic(3)
default:
return 4
}
println() // ok
}
var _ = func() int {
print(1)
switch x.(type) {
default:
return 4
case int:
print(2)
panic(3)
}
println() // ok
}
var _ = func() int {
print(1)
L:
switch x.(type) {
case int:
print(2)
for {
break L
}
default:
return 4
}
println() // ok
}
// again, but without the leading print(1).
// testing that everything works when the terminating statement is first.
var _ = func() int {
println() // ok
}
var _ = func() int {
return 2
}
var _ = func() int {
L:
goto L
}
var _ = func() int {
panic(2)
}
// but only builtin panic
var _ = func() int {
var panic = func(int) {}
panic(2)
println() // ok
}
var _ = func() int {
{
return 2
}
}
var _ = func() int {
{
return 2
}
}
var _ = func() int {
L:
{
goto L
}
}
var _ = func() int {
L:
{
goto L
}
}
var _ = func() int {
{
panic(2)
}
}
var _ = func() int {
{
panic(2)
}
}
var _ = func() int {
return 2
println() // ok
}
var _ = func() int {
L:
goto L
println() // ok
}
var _ = func() int {
panic(2)
println() // ok
}
var _ = func() int {
{
return 2
}
println() // ok
}
var _ = func() int {
L:
{
goto L
}
println() // ok
}
var _ = func() int {
{
panic(2)
}
println() // ok
}
var _ = func() int {
{
return 2
}
println() // ok
}
var _ = func() int {
L:
{
goto L
}
println() // ok
}
var _ = func() int {
{
panic(2)
}
println() // ok
}
func _() int {
// Empty switch tag with non-bool case value used to panic.
switch {
case 1:
println()
}
println()
}
================================================
FILE: go/analysis/passes/unreachable/unreachable.go
================================================
// Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package unreachable
// TODO(adonovan): use the new cfg package, which is more precise.
import (
_ "embed"
"go/ast"
"go/token"
"log"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/ast/inspector"
"golang.org/x/tools/internal/analysis/analyzerutil"
"golang.org/x/tools/internal/refactor"
)
//go:embed doc.go
var doc string
var Analyzer = &analysis.Analyzer{
Name: "unreachable",
Doc: analyzerutil.MustExtractDoc(doc, "unreachable"),
URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/unreachable",
Requires: []*analysis.Analyzer{inspect.Analyzer},
RunDespiteErrors: true,
Run: run,
}
func run(pass *analysis.Pass) (any, error) {
inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
nodeFilter := []ast.Node{
(*ast.FuncDecl)(nil),
(*ast.FuncLit)(nil),
}
inspect.Preorder(nodeFilter, func(n ast.Node) {
var body *ast.BlockStmt
switch n := n.(type) {
case *ast.FuncDecl:
body = n.Body
case *ast.FuncLit:
body = n.Body
}
if body == nil {
return
}
d := &deadState{
pass: pass,
hasBreak: make(map[ast.Stmt]bool),
hasGoto: make(map[string]bool),
labels: make(map[string]ast.Stmt),
}
d.findLabels(body)
d.reachable = true
d.findDead(body)
})
return nil, nil
}
type deadState struct {
pass *analysis.Pass
hasBreak map[ast.Stmt]bool
hasGoto map[string]bool
labels map[string]ast.Stmt
breakTarget ast.Stmt
reachable bool
}
// findLabels gathers information about the labels defined and used by stmt
// and about which statements break, whether a label is involved or not.
func (d *deadState) findLabels(stmt ast.Stmt) {
switch x := stmt.(type) {
default:
log.Fatalf("%s: internal error in findLabels: unexpected statement %T", d.pass.Fset.Position(x.Pos()), x)
case *ast.AssignStmt,
*ast.BadStmt,
*ast.DeclStmt,
*ast.DeferStmt,
*ast.EmptyStmt,
*ast.ExprStmt,
*ast.GoStmt,
*ast.IncDecStmt,
*ast.ReturnStmt,
*ast.SendStmt:
// no statements inside
case *ast.BlockStmt:
for _, stmt := range x.List {
d.findLabels(stmt)
}
case *ast.BranchStmt:
switch x.Tok {
case token.GOTO:
if x.Label != nil {
d.hasGoto[x.Label.Name] = true
}
case token.BREAK:
stmt := d.breakTarget
if x.Label != nil {
stmt = d.labels[x.Label.Name]
}
if stmt != nil {
d.hasBreak[stmt] = true
}
}
case *ast.IfStmt:
d.findLabels(x.Body)
if x.Else != nil {
d.findLabels(x.Else)
}
case *ast.LabeledStmt:
d.labels[x.Label.Name] = x.Stmt
d.findLabels(x.Stmt)
// These cases are all the same, but the x.Body only works
// when the specific type of x is known, so the cases cannot
// be merged.
case *ast.ForStmt:
outer := d.breakTarget
d.breakTarget = x
d.findLabels(x.Body)
d.breakTarget = outer
case *ast.RangeStmt:
outer := d.breakTarget
d.breakTarget = x
d.findLabels(x.Body)
d.breakTarget = outer
case *ast.SelectStmt:
outer := d.breakTarget
d.breakTarget = x
d.findLabels(x.Body)
d.breakTarget = outer
case *ast.SwitchStmt:
outer := d.breakTarget
d.breakTarget = x
d.findLabels(x.Body)
d.breakTarget = outer
case *ast.TypeSwitchStmt:
outer := d.breakTarget
d.breakTarget = x
d.findLabels(x.Body)
d.breakTarget = outer
case *ast.CommClause:
for _, stmt := range x.Body {
d.findLabels(stmt)
}
case *ast.CaseClause:
for _, stmt := range x.Body {
d.findLabels(stmt)
}
}
}
// findDead walks the statement looking for dead code.
// If d.reachable is false on entry, stmt itself is dead.
// When findDead returns, d.reachable tells whether the
// statement following stmt is reachable.
func (d *deadState) findDead(stmt ast.Stmt) {
// Is this a labeled goto target?
// If so, assume it is reachable due to the goto.
// This is slightly conservative, in that we don't
// check that the goto is reachable, so
// L: goto L
// will not provoke a warning.
// But it's good enough.
if x, isLabel := stmt.(*ast.LabeledStmt); isLabel && d.hasGoto[x.Label.Name] {
d.reachable = true
}
if !d.reachable {
switch stmt.(type) {
case *ast.EmptyStmt:
// do not warn about unreachable empty statements
default:
var (
inspect = d.pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
curStmt, _ = inspect.Root().FindNode(stmt)
tokFile = d.pass.Fset.File(stmt.Pos())
)
// (This call to pass.Report is a frequent source
// of diagnostics beyond EOF in a truncated file;
// see #71659.)
d.pass.Report(analysis.Diagnostic{
Pos: stmt.Pos(),
End: stmt.End(),
Message: "unreachable code",
SuggestedFixes: []analysis.SuggestedFix{{
Message: "Remove",
TextEdits: refactor.DeleteStmt(tokFile, curStmt),
}},
})
d.reachable = true // silence error about next statement
}
}
switch x := stmt.(type) {
default:
log.Fatalf("%s: internal error in findDead: unexpected statement %T", d.pass.Fset.Position(x.Pos()), x)
case *ast.AssignStmt,
*ast.BadStmt,
*ast.DeclStmt,
*ast.DeferStmt,
*ast.EmptyStmt,
*ast.GoStmt,
*ast.IncDecStmt,
*ast.SendStmt:
// no control flow
case *ast.BlockStmt:
for _, stmt := range x.List {
d.findDead(stmt)
}
case *ast.BranchStmt:
switch x.Tok {
case token.BREAK, token.GOTO, token.FALLTHROUGH:
d.reachable = false
case token.CONTINUE:
// NOTE: We accept "continue" statements as terminating.
// They are not necessary in the spec definition of terminating,
// because a continue statement cannot be the final statement
// before a return. But for the more general problem of syntactically
// identifying dead code, continue redirects control flow just
// like the other terminating statements.
d.reachable = false
}
case *ast.ExprStmt:
// Call to panic?
call, ok := x.X.(*ast.CallExpr)
if ok {
name, ok := call.Fun.(*ast.Ident)
if ok && name.Name == "panic" && name.Obj == nil {
d.reachable = false
}
}
case *ast.ForStmt:
d.findDead(x.Body)
d.reachable = x.Cond != nil || d.hasBreak[x]
case *ast.IfStmt:
d.findDead(x.Body)
if x.Else != nil {
r := d.reachable
d.reachable = true
d.findDead(x.Else)
d.reachable = d.reachable || r
} else {
// might not have executed if statement
d.reachable = true
}
case *ast.LabeledStmt:
d.findDead(x.Stmt)
case *ast.RangeStmt:
d.findDead(x.Body)
d.reachable = true
case *ast.ReturnStmt:
d.reachable = false
case *ast.SelectStmt:
// NOTE: Unlike switch and type switch below, we don't care
// whether a select has a default, because a select without a
// default blocks until one of the cases can run. That's different
// from a switch without a default, which behaves like it has
// a default with an empty body.
anyReachable := false
for _, comm := range x.Body.List {
d.reachable = true
for _, stmt := range comm.(*ast.CommClause).Body {
d.findDead(stmt)
}
anyReachable = anyReachable || d.reachable
}
d.reachable = anyReachable || d.hasBreak[x]
case *ast.SwitchStmt:
anyReachable := false
hasDefault := false
for _, cas := range x.Body.List {
cc := cas.(*ast.CaseClause)
if cc.List == nil {
hasDefault = true
}
d.reachable = true
for _, stmt := range cc.Body {
d.findDead(stmt)
}
anyReachable = anyReachable || d.reachable
}
d.reachable = anyReachable || d.hasBreak[x] || !hasDefault
case *ast.TypeSwitchStmt:
anyReachable := false
hasDefault := false
for _, cas := range x.Body.List {
cc := cas.(*ast.CaseClause)
if cc.List == nil {
hasDefault = true
}
d.reachable = true
for _, stmt := range cc.Body {
d.findDead(stmt)
}
anyReachable = anyReachable || d.reachable
}
d.reachable = anyReachable || d.hasBreak[x] || !hasDefault
}
}
================================================
FILE: go/analysis/passes/unreachable/unreachable_test.go
================================================
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package unreachable_test
import (
"testing"
"golang.org/x/tools/go/analysis/analysistest"
"golang.org/x/tools/go/analysis/passes/unreachable"
)
func Test(t *testing.T) {
testdata := analysistest.TestData()
analysistest.RunWithSuggestedFixes(t, testdata, unreachable.Analyzer, "a")
}
================================================
FILE: go/analysis/passes/unsafeptr/doc.go
================================================
// Copyright 2023 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package unsafeptr defines an Analyzer that checks for invalid
// conversions of uintptr to unsafe.Pointer.
//
// # Analyzer unsafeptr
//
// unsafeptr: check for invalid conversions of uintptr to unsafe.Pointer
//
// The unsafeptr analyzer reports likely incorrect uses of unsafe.Pointer
// to convert integers to pointers. A conversion from uintptr to
// unsafe.Pointer is invalid if it implies that there is a uintptr-typed
// word in memory that holds a pointer value, because that word will be
// invisible to stack copying and to the garbage collector.
package unsafeptr
================================================
FILE: go/analysis/passes/unsafeptr/testdata/src/a/a.go
================================================
// Copyright 2014 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package a
import (
"reflect"
"unsafe"
)
func f() {
var x unsafe.Pointer
var y uintptr
x = unsafe.Pointer(y) // want "possible misuse of unsafe.Pointer"
y = uintptr(x)
// only allowed pointer arithmetic is ptr +/-/&^ num.
// num+ptr is technically okay but still flagged: write ptr+num instead.
x = unsafe.Pointer(uintptr(x) + 1)
x = unsafe.Pointer(((uintptr((x))) + 1))
x = unsafe.Pointer(1 + uintptr(x)) // want "possible misuse of unsafe.Pointer"
x = unsafe.Pointer(uintptr(x) + uintptr(x)) // want "possible misuse of unsafe.Pointer"
x = unsafe.Pointer(uintptr(x) - 1)
x = unsafe.Pointer(1 - uintptr(x)) // want "possible misuse of unsafe.Pointer"
x = unsafe.Pointer(uintptr(x) &^ 3)
x = unsafe.Pointer(1 &^ uintptr(x)) // want "possible misuse of unsafe.Pointer"
// certain uses of reflect are okay
var v reflect.Value
x = unsafe.Pointer(v.Pointer())
x = unsafe.Pointer(v.Pointer() + 1) // want "possible misuse of unsafe.Pointer"
x = unsafe.Pointer(v.UnsafeAddr())
x = unsafe.Pointer((v.UnsafeAddr()))
var s1 *reflect.StringHeader
x = unsafe.Pointer(s1.Data)
x = unsafe.Pointer(s1.Data + 1) // want "possible misuse of unsafe.Pointer"
var s2 *reflect.SliceHeader
x = unsafe.Pointer(s2.Data)
var s3 reflect.StringHeader
x = unsafe.Pointer(s3.Data) // want "possible misuse of unsafe.Pointer"
var s4 reflect.SliceHeader
x = unsafe.Pointer(s4.Data) // want "possible misuse of unsafe.Pointer"
// but only in reflect
var vv V
x = unsafe.Pointer(vv.Pointer()) // want "possible misuse of unsafe.Pointer"
x = unsafe.Pointer(vv.UnsafeAddr()) // want "possible misuse of unsafe.Pointer"
var ss1 *StringHeader
x = unsafe.Pointer(ss1.Data) // want "possible misuse of unsafe.Pointer"
var ss2 *SliceHeader
x = unsafe.Pointer(ss2.Data) // want "possible misuse of unsafe.Pointer"
}
type V interface {
Pointer() uintptr
UnsafeAddr() uintptr
}
type StringHeader struct {
Data uintptr
}
type SliceHeader struct {
Data uintptr
}
================================================
FILE: go/analysis/passes/unsafeptr/testdata/src/a/issue40701.go
================================================
// Copyright 2020 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package a
import (
"reflect"
"unsafe"
)
// Explicitly allocating a variable of type reflect.SliceHeader.
func _(p *byte, n int) []byte {
var sh reflect.SliceHeader
sh.Data = uintptr(unsafe.Pointer(p))
sh.Len = n
sh.Cap = n
return *(*[]byte)(unsafe.Pointer(&sh)) // want "possible misuse of reflect.SliceHeader"
}
// Implicitly allocating a variable of type reflect.SliceHeader.
func _(p *byte, n int) []byte {
return *(*[]byte)(unsafe.Pointer(&reflect.SliceHeader{ // want "possible misuse of reflect.SliceHeader"
Data: uintptr(unsafe.Pointer(p)),
Len: n,
Cap: n,
}))
}
// Use reflect.StringHeader as a composite literal value.
func _(p *byte, n int) []byte {
var res []byte
*(*reflect.StringHeader)(unsafe.Pointer(&res)) = reflect.StringHeader{ // want "possible misuse of reflect.StringHeader"
Data: uintptr(unsafe.Pointer(p)),
Len: n,
}
return res
}
func _() {
// don't crash when obj.Pkg() == nil
var err error
_ = &err
}
================================================
FILE: go/analysis/passes/unsafeptr/testdata/src/typeparams/typeparams.go
================================================
// Copyright 2021 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package typeparams
import "unsafe"
func _[IntPtr ~uintptr, RealPtr *T, AnyPtr uintptr | *T, T any]() {
var (
i IntPtr
r RealPtr
a AnyPtr
)
_ = unsafe.Pointer(i) // incorrect, but not detected
_ = unsafe.Pointer(i + i) // incorrect, but not detected
_ = unsafe.Pointer(1 + i) // incorrect, but not detected
_ = unsafe.Pointer(uintptr(i)) // want "possible misuse of unsafe.Pointer"
_ = unsafe.Pointer(r)
_ = unsafe.Pointer(a) // possibly incorrect, but not detected
}
================================================
FILE: go/analysis/passes/unsafeptr/unsafeptr.go
================================================
// Copyright 2014 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package unsafeptr defines an Analyzer that checks for invalid
// conversions of uintptr to unsafe.Pointer.
package unsafeptr
import (
_ "embed"
"go/ast"
"go/token"
"go/types"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/ast/inspector"
"golang.org/x/tools/internal/analysis/analyzerutil"
"golang.org/x/tools/internal/typesinternal"
)
//go:embed doc.go
var doc string
var Analyzer = &analysis.Analyzer{
Name: "unsafeptr",
Doc: analyzerutil.MustExtractDoc(doc, "unsafeptr"),
URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/unsafeptr",
Requires: []*analysis.Analyzer{inspect.Analyzer},
Run: run,
}
func run(pass *analysis.Pass) (any, error) {
inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
nodeFilter := []ast.Node{
(*ast.CallExpr)(nil),
(*ast.StarExpr)(nil),
(*ast.UnaryExpr)(nil),
}
inspect.Preorder(nodeFilter, func(n ast.Node) {
switch x := n.(type) {
case *ast.CallExpr:
if len(x.Args) == 1 &&
hasBasicType(pass.TypesInfo, x.Fun, types.UnsafePointer) &&
hasBasicType(pass.TypesInfo, x.Args[0], types.Uintptr) &&
!isSafeUintptr(pass.TypesInfo, x.Args[0]) {
pass.ReportRangef(x, "possible misuse of unsafe.Pointer")
}
case *ast.StarExpr:
if t := pass.TypesInfo.Types[x].Type; isReflectHeader(t) {
pass.ReportRangef(x, "possible misuse of %s", t)
}
case *ast.UnaryExpr:
if x.Op != token.AND {
return
}
if t := pass.TypesInfo.Types[x.X].Type; isReflectHeader(t) {
pass.ReportRangef(x, "possible misuse of %s", t)
}
}
})
return nil, nil
}
// isSafeUintptr reports whether x - already known to be a uintptr -
// is safe to convert to unsafe.Pointer.
func isSafeUintptr(info *types.Info, x ast.Expr) bool {
// Check unsafe.Pointer safety rules according to
// https://golang.org/pkg/unsafe/#Pointer.
switch x := ast.Unparen(x).(type) {
case *ast.SelectorExpr:
// "(6) Conversion of a reflect.SliceHeader or
// reflect.StringHeader Data field to or from Pointer."
if x.Sel.Name != "Data" {
break
}
// reflect.SliceHeader and reflect.StringHeader are okay,
// but only if they are pointing at a real slice or string.
// It's not okay to do:
// var x SliceHeader
// x.Data = uintptr(unsafe.Pointer(...))
// ... use x ...
// p := unsafe.Pointer(x.Data)
// because in the middle the garbage collector doesn't
// see x.Data as a pointer and so x.Data may be dangling
// by the time we get to the conversion at the end.
// For now approximate by saying that *Header is okay
// but Header is not.
pt, ok := types.Unalias(info.Types[x.X].Type).(*types.Pointer)
if ok && isReflectHeader(pt.Elem()) {
return true
}
case *ast.CallExpr:
// "(5) Conversion of the result of reflect.Value.Pointer or
// reflect.Value.UnsafeAddr from uintptr to Pointer."
if len(x.Args) != 0 {
break
}
sel, ok := x.Fun.(*ast.SelectorExpr)
if !ok {
break
}
switch sel.Sel.Name {
case "Pointer", "UnsafeAddr":
if typesinternal.IsTypeNamed(info.Types[sel.X].Type, "reflect", "Value") {
return true
}
}
}
// "(3) Conversion of a Pointer to a uintptr and back, with arithmetic."
return isSafeArith(info, x)
}
// isSafeArith reports whether x is a pointer arithmetic expression that is safe
// to convert to unsafe.Pointer.
func isSafeArith(info *types.Info, x ast.Expr) bool {
switch x := ast.Unparen(x).(type) {
case *ast.CallExpr:
// Base case: initial conversion from unsafe.Pointer to uintptr.
return len(x.Args) == 1 &&
hasBasicType(info, x.Fun, types.Uintptr) &&
hasBasicType(info, x.Args[0], types.UnsafePointer)
case *ast.BinaryExpr:
// "It is valid both to add and to subtract offsets from a
// pointer in this way. It is also valid to use &^ to round
// pointers, usually for alignment."
switch x.Op {
case token.ADD, token.SUB, token.AND_NOT:
// TODO(mdempsky): Match compiler
// semantics. ADD allows a pointer on either
// side; SUB and AND_NOT don't care about RHS.
return isSafeArith(info, x.X) && !isSafeArith(info, x.Y)
}
}
return false
}
// hasBasicType reports whether x's type is a types.Basic with the given kind.
func hasBasicType(info *types.Info, x ast.Expr, kind types.BasicKind) bool {
t := info.Types[x].Type
if t != nil {
t = t.Underlying()
}
b, ok := t.(*types.Basic)
return ok && b.Kind() == kind
}
// isReflectHeader reports whether t is reflect.SliceHeader or reflect.StringHeader.
func isReflectHeader(t types.Type) bool {
return typesinternal.IsTypeNamed(t, "reflect", "SliceHeader", "StringHeader")
}
================================================
FILE: go/analysis/passes/unsafeptr/unsafeptr_test.go
================================================
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package unsafeptr_test
import (
"testing"
"golang.org/x/tools/go/analysis/analysistest"
"golang.org/x/tools/go/analysis/passes/unsafeptr"
)
func Test(t *testing.T) {
testdata := analysistest.TestData()
analysistest.Run(t, testdata, unsafeptr.Analyzer, "a", "typeparams")
}
================================================
FILE: go/analysis/passes/unusedresult/cmd/unusedresult/main.go
================================================
// Copyright 2023 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// The unusedresult command applies the golang.org/x/tools/go/analysis/passes/unusedresult
// analysis to the specified packages of Go source code.
package main
import (
"golang.org/x/tools/go/analysis/passes/unusedresult"
"golang.org/x/tools/go/analysis/singlechecker"
)
func main() { singlechecker.Main(unusedresult.Analyzer) }
================================================
FILE: go/analysis/passes/unusedresult/doc.go
================================================
// Copyright 2023 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package unusedresult defines an analyzer that checks for unused
// results of calls to certain pure functions.
//
// # Analyzer unusedresult
//
// unusedresult: check for unused results of calls to some functions
//
// Some functions like fmt.Errorf return a result and have no side
// effects, so it is always a mistake to discard the result. Other
// functions may return an error that must not be ignored, or a cleanup
// operation that must be called. This analyzer reports calls to
// functions like these when the result of the call is ignored.
//
// The set of functions may be controlled using flags.
package unusedresult
================================================
FILE: go/analysis/passes/unusedresult/testdata/src/a/a.go
================================================
// Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package a
import (
"bytes"
"errors"
"fmt"
. "fmt"
)
func _() {
fmt.Errorf("") // want "result of fmt.Errorf call not used"
_ = fmt.Errorf("")
errors.New("") // want "result of errors.New call not used"
err := errors.New("")
err.Error() // want `result of \(error\).Error call not used`
var buf bytes.Buffer
buf.String() // want `result of \(\*bytes.Buffer\).String call not used`
fmt.Sprint("") // want "result of fmt.Sprint call not used"
fmt.Sprintf("") // want "result of fmt.Sprintf call not used"
Sprint("") // want "result of fmt.Sprint call not used"
Sprintf("") // want "result of fmt.Sprintf call not used"
}
================================================
FILE: go/analysis/passes/unusedresult/testdata/src/typeparams/typeparams.go
================================================
// Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package typeparams
import (
"bytes"
"errors"
"fmt"
"typeparams/userdefs"
)
func _[T any]() {
fmt.Errorf("") // want "result of fmt.Errorf call not used"
_ = fmt.Errorf("")
errors.New("") // want "result of errors.New call not used"
err := errors.New("")
err.Error() // want `result of \(error\).Error call not used`
var buf bytes.Buffer
buf.String() // want `result of \(\*bytes.Buffer\).String call not used`
fmt.Sprint("") // want "result of fmt.Sprint call not used"
fmt.Sprintf("") // want "result of fmt.Sprintf call not used"
userdefs.MustUse[int](1) // want "result of typeparams/userdefs.MustUse call not used"
_ = userdefs.MustUse[int](2)
s := userdefs.SingleTypeParam[int]{X: 1}
s.String() // want `result of \(\*typeparams/userdefs.SingleTypeParam\[int\]\).String call not used`
_ = s.String()
m := userdefs.MultiTypeParam[int, string]{X: 1, Y: "one"}
m.String() // want `result of \(\*typeparams/userdefs.MultiTypeParam\[int, string\]\).String call not used`
_ = m.String()
}
================================================
FILE: go/analysis/passes/unusedresult/testdata/src/typeparams/userdefs/userdefs.go
================================================
// Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package userdefs
func MustUse[T interface{ ~int }](v T) T {
return v + 1
}
type SingleTypeParam[T any] struct {
X T
}
func (_ *SingleTypeParam[T]) String() string {
return "SingleTypeParam"
}
type MultiTypeParam[T any, U any] struct {
X T
Y U
}
func (_ *MultiTypeParam[T, U]) String() string {
return "MultiTypeParam"
}
================================================
FILE: go/analysis/passes/unusedresult/unusedresult.go
================================================
// Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package unusedresult defines an analyzer that checks for unused
// results of calls to certain functions.
package unusedresult
// It is tempting to make this analysis inductive: for each function
// that tail-calls one of the functions that we check, check those
// functions too. However, just because you must use the result of
// fmt.Sprintf doesn't mean you need to use the result of every
// function that returns a formatted string: it may have other results
// and effects.
import (
_ "embed"
"go/ast"
"go/token"
"go/types"
"sort"
"strings"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/ast/inspector"
"golang.org/x/tools/go/types/typeutil"
"golang.org/x/tools/internal/analysis/analyzerutil"
"golang.org/x/tools/internal/astutil"
)
//go:embed doc.go
var doc string
var Analyzer = &analysis.Analyzer{
Name: "unusedresult",
Doc: analyzerutil.MustExtractDoc(doc, "unusedresult"),
URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/unusedresult",
Requires: []*analysis.Analyzer{inspect.Analyzer},
Run: run,
}
// flags
var funcs, stringMethods stringSetFlag
func init() {
// TODO(adonovan): provide a comment or declaration syntax to
// allow users to add their functions to this set using facts.
// For example:
//
// func ignoringTheErrorWouldBeVeryBad() error {
// type mustUseResult struct{} // enables vet unusedresult check
// ...
// }
//
// ignoringTheErrorWouldBeVeryBad() // oops
//
// List standard library functions here.
// The context.With{Cancel,Deadline,Timeout} entries are
// effectively redundant wrt the lostcancel analyzer.
funcs = stringSetFlag{
"context.WithCancel": true,
"context.WithDeadline": true,
"context.WithTimeout": true,
"context.WithValue": true,
"errors.New": true,
"fmt.Append": true,
"fmt.Appendf": true,
"fmt.Appendln": true,
"fmt.Errorf": true,
"fmt.Sprint": true,
"fmt.Sprintf": true,
"fmt.Sprintln": true,
"maps.All": true,
"maps.Clone": true,
"maps.Collect": true,
"maps.Equal": true,
"maps.EqualFunc": true,
"maps.Keys": true,
"maps.Values": true,
"slices.All": true,
"slices.AppendSeq": true,
"slices.Backward": true,
"slices.BinarySearch": true,
"slices.BinarySearchFunc": true,
"slices.Chunk": true,
"slices.Clip": true,
"slices.Clone": true,
"slices.Collect": true,
"slices.Compact": true,
"slices.CompactFunc": true,
"slices.Compare": true,
"slices.CompareFunc": true,
"slices.Concat": true,
"slices.Contains": true,
"slices.ContainsFunc": true,
"slices.Delete": true,
"slices.DeleteFunc": true,
"slices.Equal": true,
"slices.EqualFunc": true,
"slices.Grow": true,
"slices.Index": true,
"slices.IndexFunc": true,
"slices.Insert": true,
"slices.IsSorted": true,
"slices.IsSortedFunc": true,
"slices.Max": true,
"slices.MaxFunc": true,
"slices.Min": true,
"slices.MinFunc": true,
"slices.Repeat": true,
"slices.Replace": true,
"slices.Sorted": true,
"slices.SortedFunc": true,
"slices.SortedStableFunc": true,
"slices.Values": true,
"sort.Reverse": true,
}
Analyzer.Flags.Var(&funcs, "funcs",
"comma-separated list of functions whose results must be used")
stringMethods.Set("Error,String")
Analyzer.Flags.Var(&stringMethods, "stringmethods",
"comma-separated list of names of methods of type func() string whose results must be used")
}
func run(pass *analysis.Pass) (any, error) {
inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
// Split functions into (pkg, name) pairs to save allocation later.
pkgFuncs := make(map[[2]string]bool, len(funcs))
for s := range funcs {
if i := strings.LastIndexByte(s, '.'); i > 0 {
pkgFuncs[[2]string{s[:i], s[i+1:]}] = true
}
}
nodeFilter := []ast.Node{
(*ast.ExprStmt)(nil),
}
inspect.Preorder(nodeFilter, func(n ast.Node) {
call, ok := ast.Unparen(n.(*ast.ExprStmt).X).(*ast.CallExpr)
if !ok {
return // not a call statement
}
// Call to function or method?
fn, ok := typeutil.Callee(pass.TypesInfo, call).(*types.Func)
if !ok {
return // e.g. var or builtin
}
if sig := fn.Signature(); sig.Recv() != nil {
// method (e.g. foo.String())
if types.Identical(sig, sigNoArgsStringResult) {
if stringMethods[fn.Name()] {
pass.ReportRangef(astutil.RangeOf(call.Pos(), call.Lparen),
"result of (%s).%s call not used",
sig.Recv().Type(), fn.Name())
}
}
} else {
// package-level function (e.g. fmt.Errorf)
if pkgFuncs[[2]string{fn.Pkg().Path(), fn.Name()}] {
pass.ReportRangef(astutil.RangeOf(call.Pos(), call.Lparen),
"result of %s.%s call not used",
fn.Pkg().Path(), fn.Name())
}
}
})
return nil, nil
}
// func() string
var sigNoArgsStringResult = types.NewSignatureType(nil, nil, nil, nil, types.NewTuple(types.NewParam(token.NoPos, nil, "", types.Typ[types.String])), false)
type stringSetFlag map[string]bool
func (ss *stringSetFlag) String() string {
var items []string
for item := range *ss {
items = append(items, item)
}
sort.Strings(items)
return strings.Join(items, ",")
}
func (ss *stringSetFlag) Set(s string) error {
m := make(map[string]bool) // clobber previous value
if s != "" {
for name := range strings.SplitSeq(s, ",") {
if name == "" {
continue // TODO: report error? proceed?
}
m[name] = true
}
}
*ss = m
return nil
}
================================================
FILE: go/analysis/passes/unusedresult/unusedresult_test.go
================================================
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package unusedresult_test
import (
"testing"
"golang.org/x/tools/go/analysis/analysistest"
"golang.org/x/tools/go/analysis/passes/unusedresult"
)
func Test(t *testing.T) {
testdata := analysistest.TestData()
funcs := "typeparams/userdefs.MustUse,errors.New,fmt.Errorf,fmt.Sprintf,fmt.Sprint"
unusedresult.Analyzer.Flags.Set("funcs", funcs)
analysistest.Run(t, testdata, unusedresult.Analyzer, "a", "typeparams")
}
================================================
FILE: go/analysis/passes/unusedwrite/doc.go
================================================
// Copyright 2023 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package unusedwrite checks for unused writes to the elements of a struct or array object.
//
// # Analyzer unusedwrite
//
// unusedwrite: checks for unused writes
//
// The analyzer reports instances of writes to struct fields and
// arrays that are never read. Specifically, when a struct object
// or an array is copied, its elements are copied implicitly by
// the compiler, and any element write to this copy does nothing
// with the original object.
//
// For example:
//
// type T struct { x int }
//
// func f(input []T) {
// for i, v := range input { // v is a copy
// v.x = i // unused write to field x
// }
// }
//
// Another example is about non-pointer receiver:
//
// type T struct { x int }
//
// func (t T) f() { // t is a copy
// t.x = i // unused write to field x
// }
package unusedwrite
================================================
FILE: go/analysis/passes/unusedwrite/main.go
================================================
// Copyright 2024 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build ignore
// The unusedwrite command runs the unusedwrite analyzer
// on the specified packages.
package main
import (
"golang.org/x/tools/go/analysis/passes/unusedwrite"
"golang.org/x/tools/go/analysis/singlechecker"
)
func main() { singlechecker.Main(unusedwrite.Analyzer) }
================================================
FILE: go/analysis/passes/unusedwrite/testdata/src/a/unusedwrite.go
================================================
package a
type T1 struct{ x int }
type T2 struct {
x int
y int
}
type T3 struct{ y *T1 }
func BadWrites() {
// Test struct field writes.
var s1 T1
s1.x = 10 // want "unused write to field x"
// Test array writes.
var s2 [10]int
s2[1] = 10 // want "unused write to array index 1:int"
// Test range variables of struct type.
s3 := []T1{T1{x: 100}}
for i, v := range s3 {
v.x = i // want "unused write to field x"
}
// Test the case where a different field is read after the write.
s4 := []T2{T2{x: 1, y: 2}}
for i, v := range s4 {
v.x = i // want "unused write to field x"
_ = v.y
}
// The analyzer can handle only simple control flow.
type T struct{ x, y int }
t := new(T)
if true {
t = new(T)
} // causes t below to become phi(alloc, alloc), not a simple alloc
t.x = 1 // false negative
print(t.y)
}
func (t T1) BadValueReceiverWrite(v T2) {
t.x = 10 // want "unused write to field x"
v.y = 20 // want "unused write to field y"
}
func GoodWrites(m map[int]int) {
// A map is copied by reference such that a write will affect the original map.
m[1] = 10
// Test struct field writes.
var s1 T1
s1.x = 10
print(s1.x)
// Test array writes.
var s2 [10]int
s2[1] = 10
// Current the checker doesn't distinguish index 1 and index 2.
_ = s2[2]
// Test range variables of struct type.
s3 := []T1{T1{x: 100}}
for i, v := range s3 { // v is a copy
v.x = i
_ = v.x // still a usage
}
// Test an object with multiple fields.
o := &T2{x: 10, y: 20}
print(o)
// Test an object of embedded struct/pointer type.
t1 := &T1{x: 10}
t2 := &T3{y: t1}
print(t2)
}
func (t *T1) GoodPointerReceiverWrite(v *T2) {
t.x = 10
v.y = 20
}
================================================
FILE: go/analysis/passes/unusedwrite/testdata/src/importsunsafe/i.go
================================================
package importsunsafe
import "unsafe"
type S struct {
F, G int
}
func _() {
var s S
s.F = 1
// This write to G is used below, because &s.F allows access to all of s, but
// the analyzer would naively report it as unused. For this reason, we
// silence the analysis if unsafe is imported.
s.G = 2
ptr := unsafe.Pointer(&s.F)
t := (*S)(ptr)
println(t.G)
}
================================================
FILE: go/analysis/passes/unusedwrite/unusedwrite.go
================================================
// Copyright 2021 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package unusedwrite
import (
_ "embed"
"go/types"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/buildssa"
"golang.org/x/tools/go/ssa"
"golang.org/x/tools/internal/analysis/analyzerutil"
"golang.org/x/tools/internal/typeparams"
)
//go:embed doc.go
var doc string
// Analyzer reports instances of writes to struct fields and arrays
// that are never read.
var Analyzer = &analysis.Analyzer{
Name: "unusedwrite",
Doc: analyzerutil.MustExtractDoc(doc, "unusedwrite"),
URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/unusedwrite",
Requires: []*analysis.Analyzer{buildssa.Analyzer},
Run: run,
}
func run(pass *analysis.Pass) (any, error) {
for _, pkg := range pass.Pkg.Imports() {
if pkg.Path() == "unsafe" {
// See golang/go#67684, or testdata/src/importsunsafe: the unusedwrite
// analyzer may have false positives when used with unsafe.
return nil, nil
}
}
ssainput := pass.ResultOf[buildssa.Analyzer].(*buildssa.SSA)
for _, fn := range ssainput.SrcFuncs {
reports := checkStores(fn)
for _, store := range reports {
switch addr := store.Addr.(type) {
case *ssa.FieldAddr:
field := typeparams.CoreType(typeparams.MustDeref(addr.X.Type())).(*types.Struct).Field(addr.Field)
pass.Reportf(store.Pos(),
"unused write to field %s", field.Name())
case *ssa.IndexAddr:
pass.Reportf(store.Pos(),
"unused write to array index %s", addr.Index)
}
}
}
return nil, nil
}
// checkStores returns *Stores in fn whose address is written to but never used.
func checkStores(fn *ssa.Function) []*ssa.Store {
var reports []*ssa.Store
// Visit each block. No need to visit fn.Recover.
for _, blk := range fn.Blocks {
for _, instr := range blk.Instrs {
// Identify writes.
if store, ok := instr.(*ssa.Store); ok {
// Consider field/index writes to an object whose elements are copied and not shared.
// MapUpdate is excluded since only the reference of the map is copied.
switch addr := store.Addr.(type) {
case *ssa.FieldAddr:
if isDeadStore(store, addr.X, addr) {
reports = append(reports, store)
}
case *ssa.IndexAddr:
if isDeadStore(store, addr.X, addr) {
reports = append(reports, store)
}
}
}
}
}
return reports
}
// isDeadStore determines whether a field/index write to an object is dead.
// Argument "obj" is the object, and "addr" is the instruction fetching the field/index.
func isDeadStore(store *ssa.Store, obj ssa.Value, addr ssa.Instruction) bool {
// Consider only struct or array objects.
if !hasStructOrArrayType(obj) {
return false
}
// Check liveness: if the value is used later, then don't report the write.
for _, ref := range *obj.Referrers() {
if ref == store || ref == addr {
continue
}
switch ins := ref.(type) {
case ssa.CallInstruction:
return false
case *ssa.FieldAddr:
// Check whether the same field is used.
if ins.X == obj {
if faddr, ok := addr.(*ssa.FieldAddr); ok {
if faddr.Field == ins.Field {
return false
}
}
}
// Otherwise another field is used, and this usage doesn't count.
continue
case *ssa.IndexAddr:
if ins.X == obj {
return false
}
continue // Otherwise another object is used
case *ssa.Lookup:
if ins.X == obj {
return false
}
continue // Otherwise another object is used
case *ssa.Store:
if ins.Val == obj {
return false
}
continue // Otherwise other object is stored
default: // consider live if the object is used in any other instruction
return false
}
}
return true
}
// isStructOrArray returns whether the underlying type is struct or array.
func isStructOrArray(tp types.Type) bool {
switch tp.Underlying().(type) {
case *types.Array:
return true
case *types.Struct:
return true
}
return false
}
// hasStructOrArrayType returns whether a value is of struct or array type.
func hasStructOrArrayType(v ssa.Value) bool {
if instr, ok := v.(ssa.Instruction); ok {
if alloc, ok := instr.(*ssa.Alloc); ok {
// Check the element type of an allocated register (which always has pointer type)
// e.g., for
// func (t T) f() { ...}
// the receiver object is of type *T:
// t0 = local T (t) *T
if tp, ok := types.Unalias(alloc.Type()).(*types.Pointer); ok {
return isStructOrArray(tp.Elem())
}
return false
}
}
return isStructOrArray(v.Type())
}
================================================
FILE: go/analysis/passes/unusedwrite/unusedwrite_test.go
================================================
// Copyright 2021 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package unusedwrite_test
import (
"testing"
"golang.org/x/tools/go/analysis/analysistest"
"golang.org/x/tools/go/analysis/passes/unusedwrite"
)
func Test(t *testing.T) {
testdata := analysistest.TestData()
analysistest.Run(t, testdata, unusedwrite.Analyzer, "a", "importsunsafe")
}
================================================
FILE: go/analysis/passes/usesgenerics/doc.go
================================================
// Copyright 2023 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package usesgenerics defines an Analyzer that checks for usage of generic
// features added in Go 1.18.
//
// # Analyzer usesgenerics
//
// usesgenerics: detect whether a package uses generics features
//
// The usesgenerics analysis reports whether a package directly or transitively
// uses certain features associated with generic programming in Go.
package usesgenerics
================================================
FILE: go/analysis/passes/usesgenerics/testdata/src/a/a.go
================================================
// want package:`features{typeDecl,funcDecl,funcInstance}`
package a
type T[P any] int
func F[P any]() {}
var _ = F[int]
================================================
FILE: go/analysis/passes/usesgenerics/testdata/src/b/b.go
================================================
// want package:`features{typeSet}`
package b
type Constraint interface {
~int | string
}
================================================
FILE: go/analysis/passes/usesgenerics/testdata/src/c/c.go
================================================
// want package:`features{typeDecl,funcDecl,typeSet,typeInstance,funcInstance}`
// Features funcDecl, typeSet, and funcInstance come from imported packages "a"
// and "b". These features are not directly present in "c".
package c
import (
"a"
"b"
)
type T[P b.Constraint] a.T[P]
================================================
FILE: go/analysis/passes/usesgenerics/testdata/src/d/d.go
================================================
// want package:`features{typeSet}`
package d
type myInt int
func _() {
// Sanity check that we can both detect local types and interfaces with
// embedded defined types.
type constraint interface {
myInt
}
}
================================================
FILE: go/analysis/passes/usesgenerics/usesgenerics.go
================================================
// Copyright 2021 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package usesgenerics
import (
_ "embed"
"reflect"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/ast/inspector"
"golang.org/x/tools/internal/analysis/analyzerutil"
"golang.org/x/tools/internal/typeparams/genericfeatures"
)
//go:embed doc.go
var doc string
var Analyzer = &analysis.Analyzer{
Name: "usesgenerics",
Doc: analyzerutil.MustExtractDoc(doc, "usesgenerics"),
URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/usesgenerics",
Requires: []*analysis.Analyzer{inspect.Analyzer},
Run: run,
ResultType: reflect.TypeFor[*Result](),
FactTypes: []analysis.Fact{new(featuresFact)},
}
type Features = genericfeatures.Features
const (
GenericTypeDecls = genericfeatures.GenericTypeDecls
GenericFuncDecls = genericfeatures.GenericFuncDecls
EmbeddedTypeSets = genericfeatures.EmbeddedTypeSets
TypeInstantiation = genericfeatures.TypeInstantiation
FuncInstantiation = genericfeatures.FuncInstantiation
)
// Result is the usesgenerics analyzer result type. The Direct field records
// features used directly by the package being analyzed (i.e. contained in the
// package source code). The Transitive field records any features used by the
// package or any of its transitive imports.
type Result struct {
Direct, Transitive Features
}
type featuresFact struct {
Features Features
}
func (f *featuresFact) AFact() {}
func (f *featuresFact) String() string { return f.Features.String() }
func run(pass *analysis.Pass) (any, error) {
inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
direct := genericfeatures.ForPackage(inspect, pass.TypesInfo)
transitive := direct | importedTransitiveFeatures(pass)
if transitive != 0 {
pass.ExportPackageFact(&featuresFact{transitive})
}
return &Result{
Direct: direct,
Transitive: transitive,
}, nil
}
// importedTransitiveFeatures computes features that are used transitively via
// imports.
func importedTransitiveFeatures(pass *analysis.Pass) Features {
var feats Features
for _, imp := range pass.Pkg.Imports() {
var importedFact featuresFact
if pass.ImportPackageFact(imp, &importedFact) {
feats |= importedFact.Features
}
}
return feats
}
================================================
FILE: go/analysis/passes/usesgenerics/usesgenerics_test.go
================================================
// Copyright 2021 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package usesgenerics_test
import (
"testing"
"golang.org/x/tools/go/analysis/analysistest"
"golang.org/x/tools/go/analysis/passes/usesgenerics"
)
func Test(t *testing.T) {
testdata := analysistest.TestData()
analysistest.Run(t, testdata, usesgenerics.Analyzer, "a", "b", "c", "d")
}
================================================
FILE: go/analysis/passes/waitgroup/doc.go
================================================
// Copyright 2024 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package waitgroup defines an Analyzer that detects simple misuses
// of sync.WaitGroup.
//
// # Analyzer waitgroup
//
// waitgroup: check for misuses of sync.WaitGroup
//
// This analyzer detects mistaken calls to the (*sync.WaitGroup).Add
// method from inside a new goroutine, causing Add to race with Wait:
//
// // WRONG
// var wg sync.WaitGroup
// go func() {
// wg.Add(1) // "WaitGroup.Add called from inside new goroutine"
// defer wg.Done()
// ...
// }()
// wg.Wait() // (may return prematurely before new goroutine starts)
//
// The correct code calls Add before starting the goroutine:
//
// // RIGHT
// var wg sync.WaitGroup
// wg.Add(1)
// go func() {
// defer wg.Done()
// ...
// }()
// wg.Wait()
package waitgroup
================================================
FILE: go/analysis/passes/waitgroup/main.go
================================================
// Copyright 2024 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build ignore
// The waitgroup command applies the golang.org/x/tools/go/analysis/passes/waitgroup
// analysis to the specified packages of Go source code.
package main
import (
"golang.org/x/tools/go/analysis/passes/waitgroup"
"golang.org/x/tools/go/analysis/singlechecker"
)
func main() { singlechecker.Main(waitgroup.Analyzer) }
================================================
FILE: go/analysis/passes/waitgroup/testdata/src/a/a.go
================================================
package a
import "sync"
func f() {
var wg sync.WaitGroup
wg.Add(1) // ok
go func() {
wg.Add(1) // want "WaitGroup.Add called from inside new goroutine"
// ...
wg.Add(1) // ok
}()
wg.Add(1) // ok
}
================================================
FILE: go/analysis/passes/waitgroup/waitgroup.go
================================================
// Copyright 2023 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package waitgroup defines an Analyzer that detects simple misuses
// of sync.WaitGroup.
package waitgroup
import (
_ "embed"
"go/ast"
"reflect"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/ast/inspector"
"golang.org/x/tools/go/types/typeutil"
"golang.org/x/tools/internal/analysis/analyzerutil"
"golang.org/x/tools/internal/typesinternal"
)
//go:embed doc.go
var doc string
var Analyzer = &analysis.Analyzer{
Name: "waitgroup",
Doc: analyzerutil.MustExtractDoc(doc, "waitgroup"),
URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/waitgroup",
Requires: []*analysis.Analyzer{inspect.Analyzer},
Run: run,
}
func run(pass *analysis.Pass) (any, error) {
if !typesinternal.Imports(pass.Pkg, "sync") {
return nil, nil // doesn't directly import sync
}
inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
nodeFilter := []ast.Node{
(*ast.CallExpr)(nil),
}
inspect.WithStack(nodeFilter, func(n ast.Node, push bool, stack []ast.Node) (proceed bool) {
if push {
call := n.(*ast.CallExpr)
obj := typeutil.Callee(pass.TypesInfo, call)
if typesinternal.IsMethodNamed(obj, "sync", "WaitGroup", "Add") &&
hasSuffix(stack, wantSuffix) &&
backindex(stack, 1) == backindex(stack, 2).(*ast.BlockStmt).List[0] { // ExprStmt must be Block's first stmt
pass.Reportf(call.Lparen, "WaitGroup.Add called from inside new goroutine")
}
}
return true
})
return nil, nil
}
// go func() {
// wg.Add(1)
// ...
// }()
var wantSuffix = []ast.Node{
(*ast.GoStmt)(nil),
(*ast.CallExpr)(nil),
(*ast.FuncLit)(nil),
(*ast.BlockStmt)(nil),
(*ast.ExprStmt)(nil),
(*ast.CallExpr)(nil),
}
// hasSuffix reports whether stack has the matching suffix,
// considering only node types.
func hasSuffix(stack, suffix []ast.Node) bool {
// TODO(adonovan): the inspector could implement this for us.
if len(stack) < len(suffix) {
return false
}
for i := range len(suffix) {
if reflect.TypeOf(backindex(stack, i)) != reflect.TypeOf(backindex(suffix, i)) {
return false
}
}
return true
}
// backindex is like [slices.Index] but from the back of the slice.
func backindex[T any](slice []T, i int) T {
return slice[len(slice)-1-i]
}
================================================
FILE: go/analysis/passes/waitgroup/waitgroup_test.go
================================================
// Copyright 2024 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package waitgroup_test
import (
"testing"
"golang.org/x/tools/go/analysis/analysistest"
"golang.org/x/tools/go/analysis/passes/waitgroup"
)
func Test(t *testing.T) {
analysistest.Run(t, analysistest.TestData(), waitgroup.Analyzer, "a")
}
================================================
FILE: go/analysis/singlechecker/singlechecker.go
================================================
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package singlechecker defines the main function for an analysis
// driver with only a single analysis.
// This package makes it easy for a provider of an analysis package to
// also provide a standalone tool that runs just that analysis.
//
// For example, if example.org/findbadness is an analysis package,
// all that is needed to define a standalone tool is a file,
// example.org/findbadness/cmd/findbadness/main.go, containing:
//
// // The findbadness command runs an analysis.
// package main
//
// import (
// "example.org/findbadness"
// "golang.org/x/tools/go/analysis/singlechecker"
// )
//
// func main() { singlechecker.Main(findbadness.Analyzer) }
package singlechecker
import (
"flag"
"fmt"
"log"
"os"
"strings"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/internal/analysisflags"
"golang.org/x/tools/go/analysis/internal/checker"
"golang.org/x/tools/go/analysis/unitchecker"
)
// Main is the main function for a checker command for a single analysis.
func Main(a *analysis.Analyzer) {
log.SetFlags(0)
log.SetPrefix(a.Name + ": ")
analyzers := []*analysis.Analyzer{a}
if err := analysis.Validate(analyzers); err != nil {
log.Fatal(err)
}
checker.RegisterFlags()
flag.Usage = func() {
paras := strings.Split(a.Doc, "\n\n")
fmt.Fprintf(os.Stderr, "%s: %s\n\n", a.Name, paras[0])
fmt.Fprintf(os.Stderr, "Usage: %s [-flag] [package]\n\n", a.Name)
if len(paras) > 1 {
fmt.Fprintln(os.Stderr, strings.Join(paras[1:], "\n\n"))
}
fmt.Fprintln(os.Stderr, "\nFlags:")
flag.PrintDefaults()
}
analyzers = analysisflags.Parse(analyzers, false)
args := flag.Args()
if len(args) == 0 {
flag.Usage()
os.Exit(1)
}
if len(args) == 1 && strings.HasSuffix(args[0], ".cfg") {
unitchecker.Run(args[0], analyzers)
panic("unreachable")
}
os.Exit(checker.Run(args, analyzers))
}
================================================
FILE: go/analysis/unitchecker/export_test.go
================================================
// Copyright 2023 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package unitchecker
import (
"go/token"
"go/types"
)
// This file exposes various internal hooks to the separate_test.
//
// TODO(adonovan): expose a public API to unitchecker that doesn't
// rely on details of JSON .cfg files or enshrine I/O decisions or
// assumptions about how "go vet" locates things. Ideally the new Run
// function would accept an interface, and a Config file would be just
// one way--the go vet way--to implement it.
func SetTypeImportExport(
MakeTypesImporter func(*Config, *token.FileSet) types.Importer,
ExportTypes func(*Config, *token.FileSet, *types.Package) error,
) {
makeTypesImporter = MakeTypesImporter
exportTypes = ExportTypes
}
================================================
FILE: go/analysis/unitchecker/main.go
================================================
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build ignore
// This file provides an example command for static checkers
// conforming to the golang.org/x/tools/go/analysis API.
// It serves as a model for the behavior of the cmd/vet tool in $GOROOT.
// Being based on the unitchecker driver, it must be run by go vet:
//
// $ go build -o unitchecker main.go
// $ go vet -vettool=unitchecker my/project/...
//
// For a checker also capable of running standalone, use multichecker.
package main
import (
"golang.org/x/tools/go/analysis/unitchecker"
"golang.org/x/tools/go/analysis/passes/appends"
"golang.org/x/tools/go/analysis/passes/asmdecl"
"golang.org/x/tools/go/analysis/passes/assign"
"golang.org/x/tools/go/analysis/passes/atomic"
"golang.org/x/tools/go/analysis/passes/bools"
"golang.org/x/tools/go/analysis/passes/buildtag"
"golang.org/x/tools/go/analysis/passes/cgocall"
"golang.org/x/tools/go/analysis/passes/composite"
"golang.org/x/tools/go/analysis/passes/copylock"
"golang.org/x/tools/go/analysis/passes/directive"
"golang.org/x/tools/go/analysis/passes/errorsas"
"golang.org/x/tools/go/analysis/passes/framepointer"
"golang.org/x/tools/go/analysis/passes/httpresponse"
"golang.org/x/tools/go/analysis/passes/ifaceassert"
"golang.org/x/tools/go/analysis/passes/loopclosure"
"golang.org/x/tools/go/analysis/passes/lostcancel"
"golang.org/x/tools/go/analysis/passes/nilfunc"
"golang.org/x/tools/go/analysis/passes/printf"
"golang.org/x/tools/go/analysis/passes/shift"
"golang.org/x/tools/go/analysis/passes/sigchanyzer"
"golang.org/x/tools/go/analysis/passes/stdmethods"
"golang.org/x/tools/go/analysis/passes/stringintconv"
"golang.org/x/tools/go/analysis/passes/structtag"
"golang.org/x/tools/go/analysis/passes/testinggoroutine"
"golang.org/x/tools/go/analysis/passes/tests"
"golang.org/x/tools/go/analysis/passes/timeformat"
"golang.org/x/tools/go/analysis/passes/unmarshal"
"golang.org/x/tools/go/analysis/passes/unreachable"
"golang.org/x/tools/go/analysis/passes/unsafeptr"
"golang.org/x/tools/go/analysis/passes/unusedresult"
)
func main() {
unitchecker.Main(
appends.Analyzer,
asmdecl.Analyzer,
assign.Analyzer,
atomic.Analyzer,
bools.Analyzer,
buildtag.Analyzer,
cgocall.Analyzer,
composite.Analyzer,
copylock.Analyzer,
directive.Analyzer,
errorsas.Analyzer,
framepointer.Analyzer,
httpresponse.Analyzer,
ifaceassert.Analyzer,
loopclosure.Analyzer,
lostcancel.Analyzer,
nilfunc.Analyzer,
printf.Analyzer,
shift.Analyzer,
sigchanyzer.Analyzer,
stdmethods.Analyzer,
stringintconv.Analyzer,
structtag.Analyzer,
tests.Analyzer,
testinggoroutine.Analyzer,
timeformat.Analyzer,
unmarshal.Analyzer,
unreachable.Analyzer,
unsafeptr.Analyzer,
unusedresult.Analyzer,
)
}
================================================
FILE: go/analysis/unitchecker/separate_test.go
================================================
// Copyright 2023 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package unitchecker_test
// This file illustrates separate analysis with an example.
import (
"bytes"
"encoding/json"
"fmt"
"go/token"
"go/types"
"io"
"os"
"path/filepath"
"sort"
"strings"
"sync/atomic"
"testing"
"golang.org/x/tools/go/analysis/passes/printf"
"golang.org/x/tools/go/analysis/unitchecker"
"golang.org/x/tools/go/gcexportdata"
"golang.org/x/tools/go/packages"
"golang.org/x/tools/internal/testenv"
"golang.org/x/tools/internal/testfiles"
"golang.org/x/tools/txtar"
)
// TestExampleSeparateAnalysis demonstrates the principle of separate
// analysis, the distribution of units of type-checking and analysis
// work across several processes, using serialized summaries to
// communicate between them.
//
// It uses two different kinds of task, "manager" and "worker":
//
// - The manager computes the graph of package dependencies, and makes
// a request to the worker for each package. It does not parse,
// type-check, or analyze Go code. It is analogous "go vet".
//
// - The worker, which contains the Analyzers, reads each request,
// loads, parses, and type-checks the files of one package,
// applies all necessary analyzers to the package, then writes
// its results to a file. It is a unitchecker-based driver,
// analogous to the program specified by go vet -vettool= flag.
//
// In practice these would be separate executables, but for simplicity
// of this example they are provided by one executable in two
// different modes: the Example function is the manager, and the same
// executable invoked with ENTRYPOINT=worker is the worker.
// (See TestIntegration for how this happens.)
//
// Unfortunately this can't be a true Example because of the skip,
// which requires a testing.T.
func TestExampleSeparateAnalysis(t *testing.T) {
testenv.NeedsGoPackages(t)
// src is an archive containing a module with a printf mistake.
const src = `
-- go.mod --
module separate
go 1.18
-- main/main.go --
package main
import "separate/lib"
func main() {
lib.MyPrintf("%s", 123)
}
-- lib/lib.go --
package lib
import "fmt"
func MyPrintf(format string, args ...any) {
fmt.Printf(format, args...)
}
`
// Expand archive into tmp tree.
fs, err := txtar.FS(txtar.Parse([]byte(src)))
if err != nil {
t.Fatal(err)
}
tmpdir := testfiles.CopyToTmp(t, fs)
// Load metadata for the main package and all its dependencies.
cfg := &packages.Config{
Mode: packages.NeedName | packages.NeedFiles | packages.NeedCompiledGoFiles | packages.NeedImports | packages.NeedModule,
Dir: tmpdir,
Env: append(os.Environ(),
"GOPROXY=off", // disable network
"GOWORK=off", // an ambient GOWORK value would break package loading
),
Logf: t.Logf,
}
pkgs, err := packages.Load(cfg, "separate/main")
if err != nil {
t.Fatal(err)
}
// Stop if any package had a metadata error.
if packages.PrintErrors(pkgs) > 0 {
t.Fatal("there were errors among loaded packages")
}
// Now we have loaded the import graph,
// let's begin the proper work of the manager.
// Gather root packages. They will get all analyzers,
// whereas dependencies get only the subset that
// produce facts or are required by them.
roots := make(map[*packages.Package]bool)
for _, pkg := range pkgs {
roots[pkg] = true
}
// nextID generates sequence numbers for each unit of work.
// We use it to create names of temporary files.
var nextID atomic.Int32
var allDiagnostics []string
// Visit all packages in postorder: dependencies first.
// TODO(adonovan): opt: use parallel postorder.
packages.Visit(pkgs, nil, func(pkg *packages.Package) {
if pkg.PkgPath == "unsafe" {
return
}
// Choose a unique prefix for temporary files
// (.cfg .types .facts) produced by this package.
// We stow it in an otherwise unused field of
// Package so it can be accessed by our importers.
prefix := fmt.Sprintf("%s/%d", tmpdir, nextID.Add(1))
pkg.ExportFile = prefix
// Construct the request to the worker.
var (
importMap = make(map[string]string)
packageFile = make(map[string]string)
packageVetx = make(map[string]string)
)
for importPath, dep := range pkg.Imports {
importMap[importPath] = dep.PkgPath
if depPrefix := dep.ExportFile; depPrefix != "" { // skip "unsafe"
packageFile[dep.PkgPath] = depPrefix + ".types"
packageVetx[dep.PkgPath] = depPrefix + ".facts"
}
}
cfg := unitchecker.Config{
ID: pkg.ID,
ImportPath: pkg.PkgPath,
GoFiles: pkg.CompiledGoFiles,
NonGoFiles: pkg.OtherFiles,
IgnoredFiles: pkg.IgnoredFiles,
ImportMap: importMap,
PackageFile: packageFile,
PackageVetx: packageVetx,
VetxOnly: !roots[pkg],
VetxOutput: prefix + ".facts",
}
if pkg.Module != nil {
if v := pkg.Module.GoVersion; v != "" {
cfg.GoVersion = "go" + v
}
cfg.ModulePath = pkg.Module.Path
cfg.ModuleVersion = pkg.Module.Version
}
// Write the JSON configuration message to a file.
cfgData, err := json.Marshal(cfg)
if err != nil {
t.Fatalf("internal error in json.Marshal: %v", err)
}
cfgFile := prefix + ".cfg"
if err := os.WriteFile(cfgFile, cfgData, 0666); err != nil {
t.Fatal(err)
}
// Send the request to the worker.
cmd := testenv.Command(t, os.Args[0], "-json", cfgFile)
cmd.Stderr = os.Stderr
cmd.Stdout = new(bytes.Buffer)
cmd.Env = append(os.Environ(), "ENTRYPOINT=worker")
if err := cmd.Run(); err != nil {
t.Fatal(err)
}
// Parse JSON output and gather in allDiagnostics.
dec := json.NewDecoder(cmd.Stdout.(io.Reader))
for {
type jsonDiagnostic struct {
Posn string `json:"posn"`
Message string `json:"message"`
}
// 'results' maps Package.Path -> Analyzer.Name -> diagnostics
var results map[string]map[string][]jsonDiagnostic
if err := dec.Decode(&results); err != nil {
if err == io.EOF {
break
}
t.Fatalf("internal error decoding JSON: %v", err)
}
for _, result := range results {
for analyzer, diags := range result {
for _, diag := range diags {
rel := strings.ReplaceAll(diag.Posn, tmpdir, "")
rel = filepath.ToSlash(rel)
msg := fmt.Sprintf("%s: [%s] %s", rel, analyzer, diag.Message)
allDiagnostics = append(allDiagnostics, msg)
}
}
}
}
})
// Observe that the example produces a fact-based diagnostic
// from separate analysis of "main", "lib", and "fmt":
const want = `/main/main.go:6:16: [printf] separate/lib.MyPrintf format %s has arg 123 of wrong type int`
sort.Strings(allDiagnostics)
if got := strings.Join(allDiagnostics, "\n"); got != want {
t.Errorf("Got: %s\nWant: %s", got, want)
}
}
// -- worker process --
// worker is the main entry point for a unitchecker-based driver
// with only a single analyzer, for illustration.
func worker() {
// Currently the unitchecker API doesn't allow clients to
// control exactly how and where fact and type information
// is produced and consumed.
//
// So, for example, it assumes that type information has
// already been produced by the compiler, which is true when
// running under "go vet", but isn't necessary. It may be more
// convenient and efficient for a distributed analysis system
// if the worker generates both of them, which is the approach
// taken in this example; they could even be saved as two
// sections of a single file.
//
// Consequently, this test currently needs special access to
// private hooks in unitchecker to control how and where facts
// and types are produced and consumed. In due course this
// will become a respectable public API. In the meantime, it
// should at least serve as a demonstration of how one could
// fork unitchecker to achieve separate analysis without go vet.
unitchecker.SetTypeImportExport(makeTypesImporter, exportTypes)
unitchecker.Main(printf.Analyzer)
}
func makeTypesImporter(cfg *unitchecker.Config, fset *token.FileSet) types.Importer {
imports := make(map[string]*types.Package)
return importerFunc(func(importPath string) (*types.Package, error) {
// Resolve import path to package path (vendoring, etc)
path, ok := cfg.ImportMap[importPath]
if !ok {
return nil, fmt.Errorf("can't resolve import %q", path)
}
if path == "unsafe" {
return types.Unsafe, nil
}
// Find, read, and decode file containing type information.
file, ok := cfg.PackageFile[path]
if !ok {
return nil, fmt.Errorf("no package file for %q", path)
}
f, err := os.Open(file)
if err != nil {
return nil, err
}
defer f.Close() // ignore error
return gcexportdata.Read(f, fset, imports, path)
})
}
func exportTypes(cfg *unitchecker.Config, fset *token.FileSet, pkg *types.Package) error {
var out bytes.Buffer
if err := gcexportdata.Write(&out, fset, pkg); err != nil {
return err
}
typesFile := strings.TrimSuffix(cfg.VetxOutput, ".facts") + ".types"
return os.WriteFile(typesFile, out.Bytes(), 0666)
}
// -- helpers --
type importerFunc func(path string) (*types.Package, error)
func (f importerFunc) Import(path string) (*types.Package, error) { return f(path) }
================================================
FILE: go/analysis/unitchecker/unitchecker.go
================================================
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// The unitchecker package defines the main function for an analysis
// driver that analyzes a single compilation unit during a build.
// It is invoked by a build system such as "go vet":
//
// $ go vet -vettool=$(which vet)
//
// It supports the following command-line protocol:
//
// -V=full describe executable (to the build tool)
// -flags describe flags (to the build tool)
// foo.cfg description of compilation unit (from the build tool)
//
// This package does not depend on go/packages.
// If you need a standalone tool, use multichecker,
// which supports this mode but can also load packages
// from source using go/packages.
package unitchecker
// TODO(adonovan):
// - with gccgo, go build does not build standard library,
// so we will not get to analyze it. Yet we must in order
// to create base facts for, say, the fmt package for the
// printf checker.
import (
"archive/zip"
"encoding/gob"
"encoding/json"
"flag"
"fmt"
"go/ast"
"go/build"
"go/importer"
"go/parser"
"go/token"
"go/types"
"io"
"log"
"os"
"path/filepath"
"reflect"
"sort"
"strings"
"sync"
"time"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/internal/analysisflags"
"golang.org/x/tools/internal/analysis/driverutil"
"golang.org/x/tools/internal/facts"
)
// A Config describes a compilation unit to be analyzed.
// It is provided to the tool in a JSON-encoded file
// whose name ends with ".cfg".
type Config struct {
ID string // e.g. "fmt [fmt.test]"
Compiler string // gc or gccgo, provided to MakeImporter
Dir string // (unused)
ImportPath string // package path
GoVersion string // minimum required Go version, such as "go1.21.0"
GoFiles []string
NonGoFiles []string
IgnoredFiles []string
ModulePath string // module path
ModuleVersion string // module version
ImportMap map[string]string // maps import path to package path
PackageFile map[string]string // maps package path to file of type information
Standard map[string]bool // package belongs to standard library
PackageVetx map[string]string // maps package path to file of fact information
VetxOnly bool // run analysis only for facts, not diagnostics
VetxOutput string // where to write file of fact information
Stdout string // write stdout (e.g. JSON, unified diff) to this file
FixArchive string // write fixed files to this zip archive, if non-empty
SucceedOnTypecheckFailure bool // obsolete awful hack; see #18395 and below
}
// Main is the main function of a vet-like analysis tool that must be
// invoked by a build system to analyze a single package.
//
// The protocol required by 'go vet -vettool=...' is that the tool must support:
//
// -flags describe flags in JSON
// -V=full describe executable for build caching
// foo.cfg perform separate modular analyze on the single
// unit described by a JSON config file foo.cfg.
// -fix don't print each diagnostic, apply its first fix
// -diff don't apply a fix, print the diff (requires -fix)
// -json print diagnostics and fixes in JSON form
func Main(analyzers ...*analysis.Analyzer) {
progname := filepath.Base(os.Args[0])
log.SetFlags(0)
log.SetPrefix(progname + ": ")
if err := analysis.Validate(analyzers); err != nil {
log.Fatal(err)
}
flag.Usage = func() {
fmt.Fprintf(os.Stderr, `%[1]s is a tool for static analysis of Go programs.
Usage of %[1]s:
%.16[1]s unit.cfg # execute analysis specified by config file
%.16[1]s help # general help, including listing analyzers and flags
%.16[1]s help name # help on specific analyzer and its flags
`, progname)
os.Exit(1)
}
analyzers = analysisflags.Parse(analyzers, true)
args := flag.Args()
if len(args) == 0 {
flag.Usage()
}
if args[0] == "help" {
analysisflags.Help(progname, analyzers, args[1:])
os.Exit(0)
}
if len(args) != 1 || !strings.HasSuffix(args[0], ".cfg") {
log.Fatalf(`invoking "go tool %[1]s" directly is unsupported; use "go %[1]s"`, progname)
}
Run(args[0], analyzers)
}
// Run reads the *.cfg file, runs the analysis,
// and calls os.Exit with an appropriate error code.
// It assumes flags have already been set.
func Run(configFile string, analyzers []*analysis.Analyzer) {
cfg, err := readConfig(configFile)
if err != nil {
log.Fatal(err)
}
// Redirect stdout to a file as requested.
if cfg.Stdout != "" {
f, err := os.Create(cfg.Stdout)
if err != nil {
log.Fatal(err)
}
os.Stdout = f
}
fset := token.NewFileSet()
results, err := run(fset, cfg, analyzers)
if err != nil {
log.Fatal(err)
}
code := 0
// In VetxOnly mode, the analysis is run only for facts.
if !cfg.VetxOnly {
code = processResults(fset, cfg.ID, cfg.FixArchive, results)
}
os.Exit(code)
}
func readConfig(filename string) (*Config, error) {
data, err := os.ReadFile(filename)
if err != nil {
return nil, err
}
cfg := new(Config)
if err := json.Unmarshal(data, cfg); err != nil {
return nil, fmt.Errorf("cannot decode JSON config file %s: %v", filename, err)
}
if len(cfg.GoFiles) == 0 {
// The go command disallows packages with no files.
// The only exception is unsafe, but the go command
// doesn't call vet on it.
return nil, fmt.Errorf("package has no files: %s", cfg.ImportPath)
}
return cfg, nil
}
func processResults(fset *token.FileSet, id, fixArchive string, results []result) (exit int) {
if analysisflags.Fix {
// Don't print the diagnostics,
// but apply all fixes from the root actions.
// Convert results to form needed by ApplyFixes.
fixActions := make([]driverutil.FixAction, len(results))
for i, res := range results {
fixActions[i] = driverutil.FixAction{
Name: res.a.Name,
Pkg: res.pkg,
Files: res.files,
FileSet: fset,
ReadFileFunc: os.ReadFile, // TODO(adonovan): respect overlays
Diagnostics: res.diagnostics,
}
}
// By default, fixes overwrite the original file.
// With the -diff flag, print the diffs to stdout.
// If "go fix" provides a fix archive, we write files
// into it so that mutations happen after the build.
write := func(filename string, content []byte) error {
return os.WriteFile(filename, content, 0644)
}
if fixArchive != "" {
f, err := os.Create(fixArchive)
if err != nil {
log.Fatalf("can't create -fix archive: %v", err)
}
zw := zip.NewWriter(f)
zw.SetComment(id) // ignore error
defer func() {
if err := zw.Close(); err != nil {
log.Fatalf("closing -fix archive zip writer: %v", err)
}
if err := f.Close(); err != nil {
log.Fatalf("closing -fix archive file: %v", err)
}
}()
write = func(filename string, content []byte) error {
f, err := zw.Create(filename)
if err != nil {
return err
}
_, err = f.Write(content)
return err
}
}
if err := driverutil.ApplyFixes(fixActions, write, analysisflags.Diff, false); err != nil {
// Fail when applying fixes failed.
log.Print(err)
exit = 1
}
// Don't proceed to print text/JSON,
// and don't report an error
// just because there were diagnostics.
return
}
// Keep consistent with analogous logic in
// printDiagnostics in ../internal/checker/checker.go.
if analysisflags.JSON {
// JSON output
tree := make(driverutil.JSONTree)
for _, res := range results {
tree.Add(fset, id, res.a.Name, res.diagnostics, res.err)
}
tree.Print(os.Stdout) // ignore error
} else {
// plain text
for _, res := range results {
if res.err != nil {
log.Println(res.err)
exit = 1
}
}
for _, res := range results {
for _, diag := range res.diagnostics {
driverutil.PrintPlain(os.Stderr, fset, analysisflags.Context, diag)
exit = 1
}
}
}
return
}
type factImporter = func(pkgPath string) ([]byte, error)
// These four hook variables are a proof of concept of a future
// parameterization of a unitchecker API that allows the client to
// determine how and where facts and types are produced and consumed.
// (Note that the eventual API will likely be quite different.)
//
// The defaults honor a Config in a manner compatible with 'go vet'.
var (
makeTypesImporter = func(cfg *Config, fset *token.FileSet) types.Importer {
compilerImporter := importer.ForCompiler(fset, cfg.Compiler, func(path string) (io.ReadCloser, error) {
// path is a resolved package path, not an import path.
file, ok := cfg.PackageFile[path]
if !ok {
if cfg.Compiler == "gccgo" && cfg.Standard[path] {
return nil, nil // fall back to default gccgo lookup
}
return nil, fmt.Errorf("no package file for %q", path)
}
return os.Open(file)
})
return importerFunc(func(importPath string) (*types.Package, error) {
path, ok := cfg.ImportMap[importPath] // resolve vendoring, etc
if !ok {
return nil, fmt.Errorf("can't resolve import %q", path)
}
return compilerImporter.Import(path)
})
}
exportTypes = func(*Config, *token.FileSet, *types.Package) error {
// By default this is a no-op, because "go vet"
// makes the compiler produce type information.
return nil
}
makeFactImporter = func(cfg *Config) factImporter {
return func(pkgPath string) ([]byte, error) {
if vetx, ok := cfg.PackageVetx[pkgPath]; ok {
return os.ReadFile(vetx)
}
return nil, nil // no .vetx file, no facts
}
}
exportFacts = func(cfg *Config, data []byte) error {
return os.WriteFile(cfg.VetxOutput, data, 0666)
}
)
func run(fset *token.FileSet, cfg *Config, analyzers []*analysis.Analyzer) ([]result, error) {
// Load, parse, typecheck.
var files []*ast.File
for _, name := range cfg.GoFiles {
f, err := parser.ParseFile(fset, name, nil, parser.ParseComments)
if err != nil {
if cfg.SucceedOnTypecheckFailure {
// Silently succeed; let the compiler
// report parse errors.
err = nil
}
return nil, err
}
files = append(files, f)
}
tc := &types.Config{
Importer: makeTypesImporter(cfg, fset),
Sizes: types.SizesFor("gc", build.Default.GOARCH), // TODO(adonovan): use cfg.Compiler
GoVersion: cfg.GoVersion,
}
info := &types.Info{
Types: make(map[ast.Expr]types.TypeAndValue),
Defs: make(map[*ast.Ident]types.Object),
Uses: make(map[*ast.Ident]types.Object),
Implicits: make(map[ast.Node]types.Object),
Instances: make(map[*ast.Ident]types.Instance),
Scopes: make(map[ast.Node]*types.Scope),
Selections: make(map[*ast.SelectorExpr]*types.Selection),
FileVersions: make(map[*ast.File]string),
}
pkg, err := tc.Check(cfg.ImportPath, fset, files, info)
if err != nil {
if cfg.SucceedOnTypecheckFailure {
// Silently succeed; let the compiler
// report type errors.
err = nil
}
return nil, err
}
// Register fact types with gob.
// In VetxOnly mode, analyzers are only for their facts,
// so we can skip any analysis that neither produces facts
// nor depends on any analysis that produces facts.
//
// TODO(adonovan): fix: the command (and logic!) here are backwards.
// It should say "...nor is required by any...". (Issue 443099)
//
// Also build a map to hold working state and result.
type action struct {
once sync.Once
result any
err error
usesFacts bool // (transitively uses)
diagnostics []analysis.Diagnostic
}
actions := make(map[*analysis.Analyzer]*action)
var registerFacts func(a *analysis.Analyzer) bool
registerFacts = func(a *analysis.Analyzer) bool {
act, ok := actions[a]
if !ok {
act = new(action)
var usesFacts bool
for _, f := range a.FactTypes {
usesFacts = true
gob.Register(f)
}
for _, req := range a.Requires {
if registerFacts(req) {
usesFacts = true
}
}
act.usesFacts = usesFacts
actions[a] = act
}
return act.usesFacts
}
var filtered []*analysis.Analyzer
for _, a := range analyzers {
if registerFacts(a) || !cfg.VetxOnly {
filtered = append(filtered, a)
}
}
analyzers = filtered
// Read facts from imported packages.
facts, err := facts.NewDecoder(pkg).Decode(makeFactImporter(cfg))
if err != nil {
return nil, err
}
// In parallel, execute the DAG of analyzers.
var exec func(a *analysis.Analyzer) *action
var execAll func(analyzers []*analysis.Analyzer)
exec = func(a *analysis.Analyzer) *action {
act := actions[a]
act.once.Do(func() {
execAll(a.Requires) // prefetch dependencies in parallel
// The inputs to this analysis are the
// results of its prerequisites.
inputs := make(map[*analysis.Analyzer]any)
var failed []string
for _, req := range a.Requires {
reqact := exec(req)
if reqact.err != nil {
failed = append(failed, req.String())
continue
}
inputs[req] = reqact.result
}
// Report an error if any dependency failed.
if failed != nil {
sort.Strings(failed)
act.err = fmt.Errorf("failed prerequisites: %s", strings.Join(failed, ", "))
return
}
factFilter := make(map[reflect.Type]bool)
for _, f := range a.FactTypes {
factFilter[reflect.TypeOf(f)] = true
}
module := &analysis.Module{
Path: cfg.ModulePath,
Version: cfg.ModuleVersion,
GoVersion: cfg.GoVersion,
}
pass := &analysis.Pass{
Analyzer: a,
Fset: fset,
Files: files,
OtherFiles: cfg.NonGoFiles,
IgnoredFiles: cfg.IgnoredFiles,
Pkg: pkg,
TypesInfo: info,
TypesSizes: tc.Sizes,
TypeErrors: nil, // unitchecker doesn't RunDespiteErrors
ResultOf: inputs,
Report: func(d analysis.Diagnostic) {
// Unitchecker doesn't apply fixes, but it does report them in the JSON output.
if err := driverutil.ValidateFixes(fset, a, d.SuggestedFixes); err != nil {
// Since we have diagnostics, the exit code will be nonzero,
// so logging these errors is sufficient.
log.Println(err)
d.SuggestedFixes = nil
}
act.diagnostics = append(act.diagnostics, d)
},
ImportObjectFact: facts.ImportObjectFact,
ExportObjectFact: facts.ExportObjectFact,
AllObjectFacts: func() []analysis.ObjectFact { return facts.AllObjectFacts(factFilter) },
ImportPackageFact: facts.ImportPackageFact,
ExportPackageFact: facts.ExportPackageFact,
AllPackageFacts: func() []analysis.PackageFact { return facts.AllPackageFacts(factFilter) },
Module: module,
}
pass.ReadFile = driverutil.CheckedReadFile(pass, os.ReadFile)
t0 := time.Now()
act.result, act.err = a.Run(pass)
if act.err == nil { // resolve URLs on diagnostics.
for i := range act.diagnostics {
if url, uerr := driverutil.ResolveURL(a, act.diagnostics[i]); uerr == nil {
act.diagnostics[i].URL = url
} else {
act.err = uerr // keep the last error
}
}
}
if false {
log.Printf("analysis %s = %s", pass, time.Since(t0))
}
})
return act
}
execAll = func(analyzers []*analysis.Analyzer) {
var wg sync.WaitGroup
for _, a := range analyzers {
wg.Add(1)
go func(a *analysis.Analyzer) {
_ = exec(a)
wg.Done()
}(a)
}
wg.Wait()
}
execAll(analyzers)
// Return diagnostics and errors from root analyzers.
results := make([]result, len(analyzers))
for i, a := range analyzers {
act := actions[a]
results[i] = result{pkg, files, a, act.diagnostics, act.err}
}
data := facts.Encode()
if err := exportFacts(cfg, data); err != nil {
return nil, fmt.Errorf("failed to export analysis facts: %v", err)
}
if err := exportTypes(cfg, fset, pkg); err != nil {
return nil, fmt.Errorf("failed to export type information: %v", err)
}
return results, nil
}
type result struct {
pkg *types.Package
files []*ast.File
a *analysis.Analyzer
diagnostics []analysis.Diagnostic
err error
}
type importerFunc func(path string) (*types.Package, error)
func (f importerFunc) Import(path string) (*types.Package, error) { return f(path) }
================================================
FILE: go/analysis/unitchecker/unitchecker_test.go
================================================
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package unitchecker_test
import (
"encoding/json"
"flag"
"fmt"
"os"
"os/exec"
"path/filepath"
"runtime"
"strings"
"testing"
"golang.org/x/tools/go/analysis/passes/assign"
"golang.org/x/tools/go/analysis/passes/findcall"
"golang.org/x/tools/go/analysis/passes/printf"
"golang.org/x/tools/go/analysis/unitchecker"
"golang.org/x/tools/internal/testenv"
"golang.org/x/tools/internal/testfiles"
"golang.org/x/tools/txtar"
)
func TestMain(m *testing.M) {
// child process?
switch os.Getenv("ENTRYPOINT") {
case "vet":
vet()
panic("unreachable")
case "minivet":
minivet()
panic("unreachable")
case "worker":
worker() // see ExampleSeparateAnalysis
panic("unreachable")
}
// test process
flag.Parse()
os.Exit(m.Run())
}
// minivet is a vet-like tool with a few analyzers, for testing.
func minivet() {
unitchecker.Main(
findcall.Analyzer,
printf.Analyzer,
assign.Analyzer,
)
}
// This is a very basic integration test of modular
// analysis with facts using unitchecker under "go vet".
// It fork/execs the main function above.
func TestIntegration(t *testing.T) {
if runtime.GOOS != "linux" && runtime.GOOS != "darwin" {
t.Skipf("skipping fork/exec test on this platform")
}
const src = `
-- go.mod --
module golang.org/fake
go 1.24
-- a/a.go --
package a
func _() {
MyFunc123()
}
func MyFunc123() {}
-- b/b.go --
package b
import "golang.org/fake/a"
func _() {
a.MyFunc123()
MyFunc123()
}
func MyFunc123() {}
-- c/c.go --
package c
func _() {
i := 5
i = i
}
-- d/d.go --
package d
import "fmt"
var (
msg string
_ = fmt.Sprintf(msg)
)
-- d/dgen.go --
// Code generated by hand. DO NOT EDIT.
package d
import "fmt"
var _ = fmt.Sprintf(msg)
`
// Expand archive into tmp tree.
fs, err := txtar.FS(txtar.Parse([]byte(src)))
if err != nil {
t.Fatal(err)
}
tmpdir := testfiles.CopyToTmp(t, fs)
// -- operators --
// vet runs "go vet" with the specified arguments (plus -findcall.name=MyFunc123).
vet := func(t *testing.T, args ...string) (exitcode int, stdout, stderr string) {
cmd := exec.Command("go", "vet", "-vettool="+os.Args[0], "-findcall.name=MyFunc123")
cmd.Stdout = new(strings.Builder)
cmd.Stderr = new(strings.Builder)
cmd.Args = append(cmd.Args, args...)
cmd.Env = append(os.Environ(), "ENTRYPOINT=minivet")
cmd.Dir = tmpdir
if err := cmd.Run(); err != nil {
exitErr, ok := err.(*exec.ExitError)
if !ok {
t.Fatalf("couldn't exec %v: %v", cmd, err)
}
exitcode = exitErr.ExitCode()
}
// Sanitize filenames; this is imperfect due to
// (e.g.) /private/tmp -> /tmp symlink on macOS.
stdout = strings.ReplaceAll(fmt.Sprint(cmd.Stdout), tmpdir, "TMPDIR")
stderr = strings.ReplaceAll(fmt.Sprint(cmd.Stderr), tmpdir, "TMPDIR")
// Show vet information on failure.
t.Cleanup(func() {
if t.Failed() {
t.Logf("command: %v", cmd)
t.Logf("exit code: %d", exitcode)
t.Logf("stdout: %s", stdout)
t.Logf("stderr: %s", stderr)
}
})
return
}
// exitcode asserts that the exit code was "want".
exitcode := func(t *testing.T, got, want int) {
if got != want {
t.Fatalf("vet tool exit code was %d", got)
}
}
// parseJSON parses the JSON diagnostics into a simple line-oriented form.
parseJSON := func(t *testing.T, stdout string) string {
var v map[string]map[string][]map[string]any
if err := json.Unmarshal([]byte(stdout), &v); err != nil {
t.Fatalf("invalid JSON: %v", err)
}
var res strings.Builder
for pkgpath, v := range v {
for analyzer, v := range v {
for _, v := range v {
fmt.Fprintf(&res, "%s: [%s@%s] %v\n",
v["posn"],
analyzer, pkgpath,
v["message"])
}
}
}
// Show parsed JSON information on failure.
t.Cleanup(func() {
if t.Failed() {
t.Logf("json: %s", &res)
}
})
return res.String()
}
// substring asserts that the labeled output contained the substring.
substring := func(t *testing.T, label, output, substr string) {
if !strings.Contains(output, substr) {
t.Fatalf("%s: expected substring %q", label, substr)
}
}
// contains asserts that the specified file contains the substring.
contains := func(t *testing.T, filename, substr string) {
content, err := os.ReadFile(filepath.Join(tmpdir, filename))
if err != nil {
t.Fatalf("can't read %s: %v", filename, err)
}
t.Cleanup(func() {
if t.Failed() {
t.Logf("content of %s: <<%s>>", filename, content)
}
})
substring(t, filename, string(content), substr)
}
// -- scenarios --
t.Run("a", func(t *testing.T) {
code, _, stderr := vet(t, "golang.org/fake/a")
exitcode(t, code, 1)
substring(t, "stderr", stderr, "a/a.go:4:11: call of MyFunc123")
})
t.Run("b", func(t *testing.T) {
code, _, stderr := vet(t, "golang.org/fake/b")
exitcode(t, code, 1)
substring(t, "stderr", stderr, "b/b.go:6:13: call of MyFunc123")
substring(t, "stderr", stderr, "b/b.go:7:11: call of MyFunc123")
})
t.Run("c", func(t *testing.T) {
code, _, stderr := vet(t, "golang.org/fake/c")
exitcode(t, code, 1)
substring(t, "stderr", stderr, "c/c.go:5:5: self-assignment of i")
})
t.Run("ab", func(t *testing.T) {
code, _, stderr := vet(t, "golang.org/fake/a", "golang.org/fake/b")
exitcode(t, code, 1)
substring(t, "stderr", stderr, "a/a.go:4:11: call of MyFunc123")
substring(t, "stderr", stderr, "b/b.go:6:13: call of MyFunc123")
substring(t, "stderr", stderr, "b/b.go:7:11: call of MyFunc123")
})
t.Run("a-json", func(t *testing.T) {
code, stdout, _ := vet(t, "-json", "golang.org/fake/a")
exitcode(t, code, 0)
testenv.NeedsGo1Point(t, 26) // depends on CL 702815 (go vet -json => stdout)
json := parseJSON(t, stdout)
substring(t, "json", json, "a/a.go:4:11: [findcall@golang.org/fake/a] call of MyFunc123")
})
t.Run("c-json", func(t *testing.T) {
code, stdout, _ := vet(t, "-json", "golang.org/fake/c")
exitcode(t, code, 0)
testenv.NeedsGo1Point(t, 26) // depends on CL 702815 (go vet -json => stdout)
json := parseJSON(t, stdout)
substring(t, "json", json, "c/c.go:5:5: [assign@golang.org/fake/c] self-assignment of i")
})
t.Run("a-context", func(t *testing.T) {
code, _, stderr := vet(t, "-c=0", "golang.org/fake/a")
exitcode(t, code, 1)
substring(t, "stderr", stderr, "a/a.go:4:11: call of MyFunc123")
substring(t, "stderr", stderr, "4 MyFunc123")
})
t.Run("d-fix", func(t *testing.T) {
testenv.NeedsGo1Point(t, 26)
code, _, stderr := vet(t, "-fix", "-v", "golang.org/fake/d")
exitcode(t, code, 0)
contains(t, "d/d.go", `fmt.Sprintf("%s", msg)`) // fixed
contains(t, "d/dgen.go", `fmt.Sprintf(msg)`) // fix not applied to generated file
// TODO(adonovan): plumb -v from go vet/fix down to unitchecker.
if false {
substring(t, "stderr", stderr, "skipped 1 fix that would edit generated files")
substring(t, "stderr", stderr, "applied 1 of 2 fixes; 1 files updated")
}
})
}
================================================
FILE: go/analysis/unitchecker/vet_std_test.go
================================================
// Copyright 2023 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package unitchecker_test
import (
"go/version"
"os"
"os/exec"
"runtime"
"strings"
"testing"
"golang.org/x/tools/go/analysis/passes/appends"
"golang.org/x/tools/go/analysis/passes/asmdecl"
"golang.org/x/tools/go/analysis/passes/assign"
"golang.org/x/tools/go/analysis/passes/atomic"
"golang.org/x/tools/go/analysis/passes/bools"
"golang.org/x/tools/go/analysis/passes/buildtag"
"golang.org/x/tools/go/analysis/passes/cgocall"
"golang.org/x/tools/go/analysis/passes/composite"
"golang.org/x/tools/go/analysis/passes/copylock"
"golang.org/x/tools/go/analysis/passes/defers"
"golang.org/x/tools/go/analysis/passes/directive"
"golang.org/x/tools/go/analysis/passes/errorsas"
"golang.org/x/tools/go/analysis/passes/framepointer"
"golang.org/x/tools/go/analysis/passes/gofix"
"golang.org/x/tools/go/analysis/passes/hostport"
"golang.org/x/tools/go/analysis/passes/httpresponse"
"golang.org/x/tools/go/analysis/passes/ifaceassert"
"golang.org/x/tools/go/analysis/passes/loopclosure"
"golang.org/x/tools/go/analysis/passes/lostcancel"
"golang.org/x/tools/go/analysis/passes/nilfunc"
"golang.org/x/tools/go/analysis/passes/printf"
"golang.org/x/tools/go/analysis/passes/shift"
"golang.org/x/tools/go/analysis/passes/sigchanyzer"
"golang.org/x/tools/go/analysis/passes/stdmethods"
"golang.org/x/tools/go/analysis/passes/stdversion"
"golang.org/x/tools/go/analysis/passes/stringintconv"
"golang.org/x/tools/go/analysis/passes/structtag"
"golang.org/x/tools/go/analysis/passes/testinggoroutine"
"golang.org/x/tools/go/analysis/passes/tests"
"golang.org/x/tools/go/analysis/passes/timeformat"
"golang.org/x/tools/go/analysis/passes/unmarshal"
"golang.org/x/tools/go/analysis/passes/unreachable"
"golang.org/x/tools/go/analysis/passes/unusedresult"
"golang.org/x/tools/go/analysis/unitchecker"
)
// vet is the entrypoint of this executable when ENTRYPOINT=vet.
// Keep consistent with the actual vet in GOROOT/src/cmd/vet/main.go.
func vet() {
unitchecker.Main(
appends.Analyzer,
asmdecl.Analyzer,
assign.Analyzer,
atomic.Analyzer,
bools.Analyzer,
buildtag.Analyzer,
cgocall.Analyzer,
composite.Analyzer,
copylock.Analyzer,
defers.Analyzer,
directive.Analyzer,
errorsas.Analyzer,
framepointer.Analyzer,
gofix.Analyzer,
httpresponse.Analyzer,
hostport.Analyzer,
ifaceassert.Analyzer,
loopclosure.Analyzer,
lostcancel.Analyzer,
nilfunc.Analyzer,
printf.Analyzer,
shift.Analyzer,
sigchanyzer.Analyzer,
stdmethods.Analyzer,
stdversion.Analyzer,
stringintconv.Analyzer,
structtag.Analyzer,
testinggoroutine.Analyzer,
tests.Analyzer,
timeformat.Analyzer,
unmarshal.Analyzer,
unreachable.Analyzer,
// unsafeptr.Analyzer, // currently reports findings in runtime
unusedresult.Analyzer,
)
}
// TestVetStdlib runs the same analyzers as the actual vet over the
// standard library, using go vet and unitchecker, to ensure that
// there are no findings.
func TestVetStdlib(t *testing.T) {
if testing.Short() {
t.Skip("skipping in -short mode")
}
if builder := os.Getenv("GO_BUILDER_NAME"); builder != "" && !strings.HasPrefix(builder, "x_tools-gotip-") {
// Run on builders like x_tools-gotip-linux-amd64-longtest,
// skip on others like x_tools-go1.24-linux-amd64-longtest.
t.Skipf("This test is only wanted on development branches where code can be easily fixed. Skipping on non-gotip builder %q.", builder)
} else if v := runtime.Version(); !strings.Contains(v, "devel") || version.Compare(v, version.Lang(v)) != 0 {
// Run on versions like "go1.25-devel_9ce47e66e8 Wed Mar 26 03:48:50 2025 -0700",
// skip on others like "go1.24.2" or "go1.24.2-devel_[…]".
t.Skipf("This test is only wanted on development versions where code can be easily fixed. Skipping on non-gotip version %q.", v)
}
cmd := exec.Command("go", "vet", "-vettool="+os.Args[0], "std")
cmd.Env = append(os.Environ(), "ENTRYPOINT=vet")
if out, err := cmd.CombinedOutput(); err != nil {
t.Errorf("go vet std failed (%v):\n%s", err, out)
}
}
================================================
FILE: go/analysis/validate.go
================================================
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package analysis
import (
"fmt"
"reflect"
"strings"
"unicode"
)
// Validate reports an error if any of the analyzers are misconfigured.
// Checks include:
// that the name is a valid identifier;
// that the Doc is not empty;
// that the Run is non-nil;
// that the Requires graph is acyclic;
// that analyzer fact types are unique;
// that each fact type is a pointer.
//
// Analyzer names need not be unique, though this may be confusing.
func Validate(analyzers []*Analyzer) error {
// Map each fact type to its sole generating analyzer.
factTypes := make(map[reflect.Type]*Analyzer)
// Traverse the Requires graph, depth first.
const (
white = iota
grey
black
finished
)
color := make(map[*Analyzer]uint8)
var visit func(a *Analyzer) error
visit = func(a *Analyzer) error {
if a == nil {
return fmt.Errorf("nil *Analyzer")
}
if color[a] == white {
color[a] = grey
// names
if !validIdent(a.Name) {
return fmt.Errorf("invalid analyzer name %q", a)
}
if a.Doc == "" {
return fmt.Errorf("analyzer %q is undocumented", a)
}
if a.Run == nil {
return fmt.Errorf("analyzer %q has nil Run", a)
}
// fact types
for _, f := range a.FactTypes {
if f == nil {
return fmt.Errorf("analyzer %s has nil FactType", a)
}
t := reflect.TypeOf(f)
if prev := factTypes[t]; prev != nil {
return fmt.Errorf("fact type %s registered by two analyzers: %v, %v",
t, a, prev)
}
if t.Kind() != reflect.Pointer {
return fmt.Errorf("%s: fact type %s is not a pointer", a, t)
}
factTypes[t] = a
}
// recursion
for _, req := range a.Requires {
if err := visit(req); err != nil {
return err
}
}
color[a] = black
}
if color[a] == grey {
stack := []*Analyzer{a}
inCycle := map[string]bool{}
for len(stack) > 0 {
current := stack[len(stack)-1]
stack = stack[:len(stack)-1]
if color[current] == grey && !inCycle[current.Name] {
inCycle[current.Name] = true
stack = append(stack, current.Requires...)
}
}
return &CycleInRequiresGraphError{AnalyzerNames: inCycle}
}
return nil
}
for _, a := range analyzers {
if err := visit(a); err != nil {
return err
}
}
// Reject duplicates among analyzers.
// Precondition: color[a] == black.
// Postcondition: color[a] == finished.
for _, a := range analyzers {
if color[a] == finished {
return fmt.Errorf("duplicate analyzer: %s", a.Name)
}
color[a] = finished
}
return nil
}
func validIdent(name string) bool {
for i, r := range name {
if !(r == '_' || unicode.IsLetter(r) || i > 0 && unicode.IsDigit(r)) {
return false
}
}
return name != ""
}
type CycleInRequiresGraphError struct {
AnalyzerNames map[string]bool
}
func (e *CycleInRequiresGraphError) Error() string {
var b strings.Builder
b.WriteString("cycle detected involving the following analyzers:")
for n := range e.AnalyzerNames {
b.WriteByte(' ')
b.WriteString(n)
}
return b.String()
}
================================================
FILE: go/analysis/validate_test.go
================================================
// Copyright 2020 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package analysis
import (
"strings"
"testing"
)
func TestValidate(t *testing.T) {
var (
run = func(p *Pass) (any, error) {
return nil, nil
}
dependsOnSelf = &Analyzer{
Name: "dependsOnSelf",
Doc: "this analyzer depends on itself",
Run: run,
}
inCycleA = &Analyzer{
Name: "inCycleA",
Doc: "this analyzer depends on inCycleB",
Run: run,
}
inCycleB = &Analyzer{
Name: "inCycleB",
Doc: "this analyzer depends on inCycleA and notInCycleA",
Run: run,
}
pointsToCycle = &Analyzer{
Name: "pointsToCycle",
Doc: "this analyzer depends on inCycleA",
Run: run,
}
notInCycleA = &Analyzer{
Name: "notInCycleA",
Doc: "this analyzer depends on notInCycleB and notInCycleC",
Run: run,
}
notInCycleB = &Analyzer{
Name: "notInCycleB",
Doc: "this analyzer depends on notInCycleC",
Run: run,
}
notInCycleC = &Analyzer{
Name: "notInCycleC",
Doc: "this analyzer has no dependencies",
Run: run,
}
)
dependsOnSelf.Requires = append(dependsOnSelf.Requires, dependsOnSelf)
inCycleA.Requires = append(inCycleA.Requires, inCycleB)
inCycleB.Requires = append(inCycleB.Requires, inCycleA, notInCycleA)
pointsToCycle.Requires = append(pointsToCycle.Requires, inCycleA)
notInCycleA.Requires = append(notInCycleA.Requires, notInCycleB, notInCycleC)
notInCycleB.Requires = append(notInCycleB.Requires, notInCycleC)
notInCycleC.Requires = []*Analyzer{}
cases := []struct {
analyzers []*Analyzer
wantErr bool
analyzersInCycle map[string]bool
}{
{
[]*Analyzer{dependsOnSelf},
true,
map[string]bool{"dependsOnSelf": true},
},
{
[]*Analyzer{inCycleA, inCycleB},
true,
map[string]bool{"inCycleA": true, "inCycleB": true},
},
{
[]*Analyzer{pointsToCycle},
true,
map[string]bool{"inCycleA": true, "inCycleB": true},
},
{
[]*Analyzer{notInCycleA},
false,
map[string]bool{},
},
}
for _, c := range cases {
got := Validate(c.analyzers)
if !c.wantErr {
if got == nil {
continue
}
t.Errorf("got unexpected error while validating analyzers %v: %v", c.analyzers, got)
}
if got == nil {
t.Errorf("expected error while validating analyzers %v, but got nil", c.analyzers)
}
err, ok := got.(*CycleInRequiresGraphError)
if !ok {
t.Errorf("want CycleInRequiresGraphError, got %T", err)
}
for a := range c.analyzersInCycle {
if !err.AnalyzerNames[a] {
t.Errorf("analyzer %s should be in cycle", a)
}
}
for a := range err.AnalyzerNames {
if !c.analyzersInCycle[a] {
t.Errorf("analyzer %s should not be in cycle", a)
}
}
}
}
func TestCycleInRequiresGraphErrorMessage(t *testing.T) {
err := CycleInRequiresGraphError{}
errMsg := err.Error()
wantSubstring := "cycle detected"
if !strings.Contains(errMsg, wantSubstring) {
t.Errorf("error string %s does not contain expected substring %q", errMsg, wantSubstring)
}
}
func TestValidateEmptyDoc(t *testing.T) {
withoutDoc := &Analyzer{
Name: "withoutDoc",
Run: func(p *Pass) (any, error) {
return nil, nil
},
}
err := Validate([]*Analyzer{withoutDoc})
if err == nil || !strings.Contains(err.Error(), "is undocumented") {
t.Errorf("got unexpected error while validating analyzers withoutDoc: %v", err)
}
}
func TestValidateNoRun(t *testing.T) {
withoutRun := &Analyzer{
Name: "withoutRun",
Doc: "this analyzer has no Run",
}
err := Validate([]*Analyzer{withoutRun})
if err == nil || !strings.Contains(err.Error(), "has nil Run") {
t.Errorf("got unexpected error while validating analyzers withoutRun: %v", err)
}
}
================================================
FILE: go/ast/astutil/enclosing.go
================================================
// Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package astutil
// This file defines utilities for working with source positions.
import (
"fmt"
"go/ast"
"go/token"
"sort"
)
// PathEnclosingInterval returns the node that encloses the source
// interval [start, end), and all its ancestors up to the AST root.
//
// The definition of "enclosing" used by this function considers
// additional whitespace abutting a node to be enclosed by it.
// In this example:
//
// z := x + y // add them
// <-A->
// <----B----->
//
// the ast.BinaryExpr(+) node is considered to enclose interval B
// even though its [Pos()..End()) is actually only interval A.
// This behaviour makes user interfaces more tolerant of imperfect
// input.
//
// This function treats tokens as nodes, though they are not included
// in the result. e.g. PathEnclosingInterval("+") returns the
// enclosing ast.BinaryExpr("x + y").
//
// If start==end, the 1-char interval following start is used instead.
//
// The 'exact' result is true if the interval contains only path[0]
// and perhaps some adjacent whitespace. It is false if the interval
// overlaps multiple children of path[0], or if it contains only
// interior whitespace of path[0].
// In this example:
//
// z := x + y // add them
// <--C--> <---E-->
// ^
// D
//
// intervals C, D and E are inexact. C is contained by the
// z-assignment statement, because it spans three of its children (:=,
// x, +). So too is the 1-char interval D, because it contains only
// interior whitespace of the assignment. E is considered interior
// whitespace of the BlockStmt containing the assignment.
//
// The resulting path is never empty; it always contains at least the
// 'root' *ast.File. Ideally PathEnclosingInterval would reject
// intervals that lie wholly or partially outside the range of the
// file, but unfortunately ast.File records only the token.Pos of
// the 'package' keyword, but not of the start of the file itself.
func PathEnclosingInterval(root *ast.File, start, end token.Pos) (path []ast.Node, exact bool) {
// fmt.Printf("EnclosingInterval %d %d\n", start, end) // debugging
// Precondition: node.[Pos..End) and adjoining whitespace contain [start, end).
var visit func(node ast.Node) bool
visit = func(node ast.Node) bool {
path = append(path, node)
nodePos := node.Pos()
nodeEnd := node.End()
// fmt.Printf("visit(%T, %d, %d)\n", node, nodePos, nodeEnd) // debugging
// Intersect [start, end) with interval of node.
if start < nodePos {
start = nodePos
}
if end > nodeEnd {
end = nodeEnd
}
// Find sole child that contains [start, end).
children := childrenOf(node)
l := len(children)
for i, child := range children {
// [childPos, childEnd) is unaugmented interval of child.
childPos := child.Pos()
childEnd := child.End()
// [augPos, augEnd) is whitespace-augmented interval of child.
augPos := childPos
augEnd := childEnd
if i > 0 {
augPos = children[i-1].End() // start of preceding whitespace
}
if i < l-1 {
nextChildPos := children[i+1].Pos()
// Does [start, end) lie between child and next child?
if start >= augEnd && end <= nextChildPos {
return false // inexact match
}
augEnd = nextChildPos // end of following whitespace
}
// fmt.Printf("\tchild %d: [%d..%d)\tcontains interval [%d..%d)?\n",
// i, augPos, augEnd, start, end) // debugging
// Does augmented child strictly contain [start, end)?
if augPos <= start && end <= augEnd {
if is[tokenNode](child) {
return true
}
// childrenOf elides the FuncType node beneath FuncDecl.
// Add it back here for TypeParams, Params, Results,
// all FieldLists). But we don't add it back for the "func" token
// even though it is the tree at FuncDecl.Type.Func.
if decl, ok := node.(*ast.FuncDecl); ok {
if fields, ok := child.(*ast.FieldList); ok && fields != decl.Recv {
path = append(path, decl.Type)
}
}
return visit(child)
}
// Does [start, end) overlap multiple children?
// i.e. left-augmented child contains start
// but LR-augmented child does not contain end.
if start < childEnd && end > augEnd {
break
}
}
// No single child contained [start, end),
// so node is the result. Is it exact?
// (It's tempting to put this condition before the
// child loop, but it gives the wrong result in the
// case where a node (e.g. ExprStmt) and its sole
// child have equal intervals.)
if start == nodePos && end == nodeEnd {
return true // exact match
}
return false // inexact: overlaps multiple children
}
// Ensure [start,end) is nondecreasing.
if start > end {
start, end = end, start
}
if start < root.End() && end > root.Pos() {
if start == end {
end = start + 1 // empty interval => interval of size 1
}
exact = visit(root)
// Reverse the path:
for i, l := 0, len(path); i < l/2; i++ {
path[i], path[l-1-i] = path[l-1-i], path[i]
}
} else {
// Selection lies within whitespace preceding the
// first (or following the last) declaration in the file.
// The result nonetheless always includes the ast.File.
path = append(path, root)
}
return
}
// tokenNode is a dummy implementation of ast.Node for a single token.
// They are used transiently by PathEnclosingInterval but never escape
// this package.
type tokenNode struct {
pos token.Pos
end token.Pos
}
func (n tokenNode) Pos() token.Pos {
return n.pos
}
func (n tokenNode) End() token.Pos {
return n.end
}
func tok(pos token.Pos, len int) ast.Node {
return tokenNode{pos, pos + token.Pos(len)}
}
// childrenOf returns the direct non-nil children of ast.Node n.
// It may include fake ast.Node implementations for bare tokens.
// it is not safe to call (e.g.) ast.Walk on such nodes.
func childrenOf(n ast.Node) []ast.Node {
var children []ast.Node
// First add nodes for all true subtrees.
ast.Inspect(n, func(node ast.Node) bool {
if node == n { // push n
return true // recur
}
if node != nil { // push child
children = append(children, node)
}
return false // no recursion
})
// TODO(adonovan): be more careful about missing (!Pos.Valid)
// tokens in trees produced from invalid input.
// Then add fake Nodes for bare tokens.
switch n := n.(type) {
case *ast.ArrayType:
children = append(children,
tok(n.Lbrack, len("[")),
tok(n.Elt.End(), len("]")))
case *ast.AssignStmt:
children = append(children,
tok(n.TokPos, len(n.Tok.String())))
case *ast.BasicLit:
children = append(children,
tok(n.ValuePos, len(n.Value)))
case *ast.BinaryExpr:
children = append(children, tok(n.OpPos, len(n.Op.String())))
case *ast.BlockStmt:
if n.Lbrace.IsValid() {
children = append(children, tok(n.Lbrace, len("{")))
}
if n.Rbrace.IsValid() {
children = append(children, tok(n.Rbrace, len("}")))
}
case *ast.BranchStmt:
children = append(children,
tok(n.TokPos, len(n.Tok.String())))
case *ast.CallExpr:
children = append(children,
tok(n.Lparen, len("(")),
tok(n.Rparen, len(")")))
if n.Ellipsis != 0 {
children = append(children, tok(n.Ellipsis, len("...")))
}
case *ast.CaseClause:
if n.List == nil {
children = append(children,
tok(n.Case, len("default")))
} else {
children = append(children,
tok(n.Case, len("case")))
}
children = append(children, tok(n.Colon, len(":")))
case *ast.ChanType:
switch n.Dir {
case ast.RECV:
children = append(children, tok(n.Begin, len("<-chan")))
case ast.SEND:
children = append(children, tok(n.Begin, len("chan<-")))
case ast.RECV | ast.SEND:
children = append(children, tok(n.Begin, len("chan")))
}
case *ast.CommClause:
if n.Comm == nil {
children = append(children,
tok(n.Case, len("default")))
} else {
children = append(children,
tok(n.Case, len("case")))
}
children = append(children, tok(n.Colon, len(":")))
case *ast.Comment:
// nop
case *ast.CommentGroup:
// nop
case *ast.CompositeLit:
children = append(children,
tok(n.Lbrace, len("{")),
tok(n.Rbrace, len("{")))
case *ast.DeclStmt:
// nop
case *ast.DeferStmt:
children = append(children,
tok(n.Defer, len("defer")))
case *ast.Ellipsis:
children = append(children,
tok(n.Ellipsis, len("...")))
case *ast.EmptyStmt:
// nop
case *ast.ExprStmt:
// nop
case *ast.Field:
// TODO(adonovan): Field.{Doc,Comment,Tag}?
case *ast.FieldList:
if n.Opening.IsValid() {
children = append(children, tok(n.Opening, len("(")))
}
if n.Closing.IsValid() {
children = append(children, tok(n.Closing, len(")")))
}
case *ast.File:
// TODO test: Doc
children = append(children,
tok(n.Package, len("package")))
case *ast.ForStmt:
children = append(children,
tok(n.For, len("for")))
case *ast.FuncDecl:
// TODO(adonovan): FuncDecl.Comment?
// Uniquely, FuncDecl breaks the invariant that
// preorder traversal yields tokens in lexical order:
// in fact, FuncDecl.Recv precedes FuncDecl.Type.Func.
//
// As a workaround, we inline the case for FuncType
// here and order things correctly.
// We also need to insert the elided FuncType just
// before the 'visit' recursion.
//
children = nil // discard ast.Walk(FuncDecl) info subtrees
children = append(children, tok(n.Type.Func, len("func")))
if n.Recv != nil {
children = append(children, n.Recv)
}
children = append(children, n.Name)
if tparams := n.Type.TypeParams; tparams != nil {
children = append(children, tparams)
}
if n.Type.Params != nil {
children = append(children, n.Type.Params)
}
if n.Type.Results != nil {
children = append(children, n.Type.Results)
}
if n.Body != nil {
children = append(children, n.Body)
}
case *ast.FuncLit:
// nop
case *ast.FuncType:
if n.Func != 0 {
children = append(children,
tok(n.Func, len("func")))
}
case *ast.GenDecl:
children = append(children,
tok(n.TokPos, len(n.Tok.String())))
if n.Lparen != 0 {
children = append(children,
tok(n.Lparen, len("(")),
tok(n.Rparen, len(")")))
}
case *ast.GoStmt:
children = append(children,
tok(n.Go, len("go")))
case *ast.Ident:
children = append(children,
tok(n.NamePos, len(n.Name)))
case *ast.IfStmt:
children = append(children,
tok(n.If, len("if")))
case *ast.ImportSpec:
// TODO(adonovan): ImportSpec.{Doc,EndPos}?
case *ast.IncDecStmt:
children = append(children,
tok(n.TokPos, len(n.Tok.String())))
case *ast.IndexExpr:
children = append(children,
tok(n.Lbrack, len("[")),
tok(n.Rbrack, len("]")))
case *ast.IndexListExpr:
children = append(children,
tok(n.Lbrack, len("[")),
tok(n.Rbrack, len("]")))
case *ast.InterfaceType:
children = append(children,
tok(n.Interface, len("interface")))
case *ast.KeyValueExpr:
children = append(children,
tok(n.Colon, len(":")))
case *ast.LabeledStmt:
children = append(children,
tok(n.Colon, len(":")))
case *ast.MapType:
children = append(children,
tok(n.Map, len("map")))
case *ast.ParenExpr:
children = append(children,
tok(n.Lparen, len("(")),
tok(n.Rparen, len(")")))
case *ast.RangeStmt:
children = append(children,
tok(n.For, len("for")),
tok(n.TokPos, len(n.Tok.String())))
case *ast.ReturnStmt:
children = append(children,
tok(n.Return, len("return")))
case *ast.SelectStmt:
children = append(children,
tok(n.Select, len("select")))
case *ast.SelectorExpr:
// nop
case *ast.SendStmt:
children = append(children,
tok(n.Arrow, len("<-")))
case *ast.SliceExpr:
children = append(children,
tok(n.Lbrack, len("[")),
tok(n.Rbrack, len("]")))
case *ast.StarExpr:
children = append(children, tok(n.Star, len("*")))
case *ast.StructType:
children = append(children, tok(n.Struct, len("struct")))
case *ast.SwitchStmt:
children = append(children, tok(n.Switch, len("switch")))
case *ast.TypeAssertExpr:
children = append(children,
tok(n.Lparen-1, len(".")),
tok(n.Lparen, len("(")),
tok(n.Rparen, len(")")))
case *ast.TypeSpec:
// TODO(adonovan): TypeSpec.{Doc,Comment}?
case *ast.TypeSwitchStmt:
children = append(children, tok(n.Switch, len("switch")))
case *ast.UnaryExpr:
children = append(children, tok(n.OpPos, len(n.Op.String())))
case *ast.ValueSpec:
// TODO(adonovan): ValueSpec.{Doc,Comment}?
case *ast.BadDecl, *ast.BadExpr, *ast.BadStmt:
// nop
}
// TODO(adonovan): opt: merge the logic of ast.Inspect() into
// the switch above so we can make interleaved callbacks for
// both Nodes and Tokens in the right order and avoid the need
// to sort.
sort.Sort(byPos(children))
return children
}
type byPos []ast.Node
func (sl byPos) Len() int {
return len(sl)
}
func (sl byPos) Less(i, j int) bool {
return sl[i].Pos() < sl[j].Pos()
}
func (sl byPos) Swap(i, j int) {
sl[i], sl[j] = sl[j], sl[i]
}
// NodeDescription returns a description of the concrete type of n suitable
// for a user interface.
//
// TODO(adonovan): in some cases (e.g. Field, FieldList, Ident,
// StarExpr) we could be much more specific given the path to the AST
// root. Perhaps we should do that.
func NodeDescription(n ast.Node) string {
switch n := n.(type) {
case *ast.ArrayType:
return "array type"
case *ast.AssignStmt:
return "assignment"
case *ast.BadDecl:
return "bad declaration"
case *ast.BadExpr:
return "bad expression"
case *ast.BadStmt:
return "bad statement"
case *ast.BasicLit:
return "basic literal"
case *ast.BinaryExpr:
return fmt.Sprintf("binary %s operation", n.Op)
case *ast.BlockStmt:
return "block"
case *ast.BranchStmt:
switch n.Tok {
case token.BREAK:
return "break statement"
case token.CONTINUE:
return "continue statement"
case token.GOTO:
return "goto statement"
case token.FALLTHROUGH:
return "fall-through statement"
}
case *ast.CallExpr:
if len(n.Args) == 1 && !n.Ellipsis.IsValid() {
return "function call (or conversion)"
}
return "function call"
case *ast.CaseClause:
return "case clause"
case *ast.ChanType:
return "channel type"
case *ast.CommClause:
return "communication clause"
case *ast.Comment:
return "comment"
case *ast.CommentGroup:
return "comment group"
case *ast.CompositeLit:
return "composite literal"
case *ast.DeclStmt:
return NodeDescription(n.Decl) + " statement"
case *ast.DeferStmt:
return "defer statement"
case *ast.Ellipsis:
return "ellipsis"
case *ast.EmptyStmt:
return "empty statement"
case *ast.ExprStmt:
return "expression statement"
case *ast.Field:
// Can be any of these:
// struct {x, y int} -- struct field(s)
// struct {T} -- anon struct field
// interface {I} -- interface embedding
// interface {f()} -- interface method
// func (A) func(B) C -- receiver, param(s), result(s)
return "field/method/parameter"
case *ast.FieldList:
return "field/method/parameter list"
case *ast.File:
return "source file"
case *ast.ForStmt:
return "for loop"
case *ast.FuncDecl:
return "function declaration"
case *ast.FuncLit:
return "function literal"
case *ast.FuncType:
return "function type"
case *ast.GenDecl:
switch n.Tok {
case token.IMPORT:
return "import declaration"
case token.CONST:
return "constant declaration"
case token.TYPE:
return "type declaration"
case token.VAR:
return "variable declaration"
}
case *ast.GoStmt:
return "go statement"
case *ast.Ident:
return "identifier"
case *ast.IfStmt:
return "if statement"
case *ast.ImportSpec:
return "import specification"
case *ast.IncDecStmt:
if n.Tok == token.INC {
return "increment statement"
}
return "decrement statement"
case *ast.IndexExpr:
return "index expression"
case *ast.IndexListExpr:
return "index list expression"
case *ast.InterfaceType:
return "interface type"
case *ast.KeyValueExpr:
return "key/value association"
case *ast.LabeledStmt:
return "statement label"
case *ast.MapType:
return "map type"
case *ast.Package:
return "package"
case *ast.ParenExpr:
return "parenthesized " + NodeDescription(n.X)
case *ast.RangeStmt:
return "range loop"
case *ast.ReturnStmt:
return "return statement"
case *ast.SelectStmt:
return "select statement"
case *ast.SelectorExpr:
return "selector"
case *ast.SendStmt:
return "channel send"
case *ast.SliceExpr:
return "slice expression"
case *ast.StarExpr:
return "*-operation" // load/store expr or pointer type
case *ast.StructType:
return "struct type"
case *ast.SwitchStmt:
return "switch statement"
case *ast.TypeAssertExpr:
return "type assertion"
case *ast.TypeSpec:
return "type specification"
case *ast.TypeSwitchStmt:
return "type switch"
case *ast.UnaryExpr:
return fmt.Sprintf("unary %s operation", n.Op)
case *ast.ValueSpec:
return "value specification"
}
panic(fmt.Sprintf("unexpected node type: %T", n))
}
func is[T any](x any) bool {
_, ok := x.(T)
return ok
}
================================================
FILE: go/ast/astutil/enclosing_test.go
================================================
// Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package astutil_test
// This file defines tests of PathEnclosingInterval.
// TODO(adonovan): exhaustive tests that run over the whole input
// tree, not just handcrafted examples.
import (
"bytes"
"fmt"
"go/ast"
"go/parser"
"go/token"
"strings"
"testing"
"golang.org/x/tools/go/ast/astutil"
)
// pathToString returns a string containing the concrete types of the
// nodes in path.
func pathToString(path []ast.Node) string {
var buf bytes.Buffer
fmt.Fprint(&buf, "[")
for i, n := range path {
if i > 0 {
fmt.Fprint(&buf, " ")
}
fmt.Fprint(&buf, strings.TrimPrefix(fmt.Sprintf("%T", n), "*ast."))
}
fmt.Fprint(&buf, "]")
return buf.String()
}
// findInterval parses input and returns the [start, end) positions of
// the first occurrence of substr in input. f==nil indicates failure;
// an error has already been reported in that case.
func findInterval(t *testing.T, fset *token.FileSet, input, substr string) (f *ast.File, start, end token.Pos) {
f, err := parser.ParseFile(fset, " ", input, 0)
if err != nil {
t.Errorf("parse error: %s", err)
return
}
i := strings.Index(input, substr)
if i < 0 {
t.Errorf("%q is not a substring of input", substr)
f = nil
return
}
filePos := fset.File(f.Package)
return f, filePos.Pos(i), filePos.Pos(i + len(substr))
}
// Common input for following tests.
const input = `
// Hello.
package main
import "fmt"
func f() {}
func main() {
z := (x + y) // add them
f() // NB: ExprStmt and its CallExpr have same Pos/End
}
func g[A any, P interface{ctype1| ~ctype2}](a1 A, p1 P) {}
type PT[T constraint] struct{ t T }
func (r recv) method(p param) {}
var v GT[targ1]
var h = g[ targ2, targ3]
`
func TestPathEnclosingInterval_Exact(t *testing.T) {
type testCase struct {
substr string // first occurrence of this string indicates interval
node string // complete text of expected containing node
}
dup := func(s string) testCase { return testCase{s, s} }
// For the exact tests, we check that a substring is mapped to
// the canonical string for the node it denotes.
tests := []testCase{
{"package",
input[11 : len(input)-1]},
{"\npack",
input[11 : len(input)-1]},
dup("main"),
{"import",
"import \"fmt\""},
dup("\"fmt\""),
{"\nfunc f() {}\n",
"func f() {}"},
{"x ",
"x"},
{" y",
"y"},
dup("z"),
{" + ",
"x + y"},
{" :=",
"z := (x + y)"},
dup("x + y"),
dup("(x + y)"),
{" (x + y) ",
"(x + y)"},
{" (x + y) // add",
"(x + y)"},
{"func",
"func f() {}"},
dup("func f() {}"),
{"\nfun",
"func f() {}"},
{" f",
"f"},
dup("[A any, P interface{ctype1| ~ctype2}]"),
{"[", "[A any, P interface{ctype1| ~ctype2}]"},
dup("A"),
{" any", "any"},
dup("ctype1"),
{"|", "ctype1| ~ctype2"},
dup("ctype2"),
{"~", "~ctype2"},
dup("~ctype2"),
{" ~ctype2", "~ctype2"},
{"]", "[A any, P interface{ctype1| ~ctype2}]"},
dup("a1"),
dup("a1 A"),
dup("(a1 A, p1 P)"),
dup("type PT[T constraint] struct{ t T }"),
dup("PT"),
dup("[T constraint]"),
dup("constraint"),
dup("targ1"),
{" targ2", "targ2"},
dup("g[ targ2, targ3]"),
}
for _, test := range tests {
f, start, end := findInterval(t, new(token.FileSet), input, test.substr)
if f == nil {
continue
}
path, exact := astutil.PathEnclosingInterval(f, start, end)
if !exact {
t.Errorf("PathEnclosingInterval(%q) not exact", test.substr)
continue
}
if len(path) == 0 {
if test.node != "" {
t.Errorf("PathEnclosingInterval(%q).path: got [], want %q",
test.substr, test.node)
}
continue
}
if got := input[path[0].Pos():path[0].End()]; got != test.node {
t.Errorf("PathEnclosingInterval(%q): got %q, want %q (path was %s)",
test.substr, got, test.node, pathToString(path))
continue
}
}
}
func TestPathEnclosingInterval_Paths(t *testing.T) {
type testCase struct {
substr string // first occurrence of this string indicates interval
path string // the pathToString(),exact of the expected path
}
// For these tests, we check only the path of the enclosing
// node, but not its complete text because it's often quite
// large when !exact.
tests := []testCase{
{"// add",
"[BlockStmt FuncDecl File],false"},
{"(x + y",
"[ParenExpr AssignStmt BlockStmt FuncDecl File],false"},
{"x +",
"[BinaryExpr ParenExpr AssignStmt BlockStmt FuncDecl File],false"},
{"z := (x",
"[AssignStmt BlockStmt FuncDecl File],false"},
{"func f",
"[FuncDecl File],false"},
{"func f()",
"[FuncDecl File],false"},
{" f()",
"[FuncDecl File],false"},
{"() {}",
"[FuncDecl File],false"},
{"// Hello",
"[File],false"},
{" f",
"[Ident FuncDecl File],true"},
{"func ",
"[FuncDecl File],true"},
{"mai",
"[Ident File],true"},
{"f() // NB",
"[CallExpr ExprStmt BlockStmt FuncDecl File],true"},
{" any", "[Ident Field FieldList FuncType FuncDecl File],true"},
{"|", "[BinaryExpr Field FieldList InterfaceType Field FieldList FuncType FuncDecl File],true"},
{"ctype2",
"[Ident UnaryExpr BinaryExpr Field FieldList InterfaceType Field FieldList FuncType FuncDecl File],true"},
{"a1", "[Ident Field FieldList FuncType FuncDecl File],true"},
{"PT[T constraint]", "[TypeSpec GenDecl File],false"},
{"[T constraint]", "[FieldList TypeSpec GenDecl File],true"},
{"targ2", "[Ident IndexListExpr ValueSpec GenDecl File],true"},
{"p param", "[Field FieldList FuncType FuncDecl File],true"}, // FuncType is present for FuncDecl.Params (etc)
{"r recv", "[Field FieldList FuncDecl File],true"}, // no FuncType for FuncDecl.Recv
}
for _, test := range tests {
f, start, end := findInterval(t, new(token.FileSet), input, test.substr)
if f == nil {
continue
}
path, exact := astutil.PathEnclosingInterval(f, start, end)
if got := fmt.Sprintf("%s,%v", pathToString(path), exact); got != test.path {
t.Errorf("PathEnclosingInterval(%q): got %q, want %q",
test.substr, got, test.path)
continue
}
}
}
================================================
FILE: go/ast/astutil/imports.go
================================================
// Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package astutil contains common utilities for working with the Go AST.
package astutil // import "golang.org/x/tools/go/ast/astutil"
import (
"fmt"
"go/ast"
"go/token"
"reflect"
"slices"
"strconv"
"strings"
)
// AddImport adds the import path to the file f, if absent.
func AddImport(fset *token.FileSet, f *ast.File, path string) (added bool) {
return AddNamedImport(fset, f, "", path)
}
// AddNamedImport adds the import with the given name and path to the file f, if absent.
// If name is not empty, it is used to rename the import.
//
// For example, calling
//
// AddNamedImport(fset, f, "pathpkg", "path")
//
// adds
//
// import pathpkg "path"
func AddNamedImport(fset *token.FileSet, f *ast.File, name, path string) (added bool) {
if imports(f, name, path) {
return false
}
newImport := &ast.ImportSpec{
Path: &ast.BasicLit{
Kind: token.STRING,
Value: strconv.Quote(path),
},
}
if name != "" {
newImport.Name = &ast.Ident{Name: name}
}
// Find an import decl to add to.
// The goal is to find an existing import
// whose import path has the longest shared
// prefix with path.
var (
bestMatch = -1 // length of longest shared prefix
lastImport = -1 // index in f.Decls of the file's final import decl
impDecl *ast.GenDecl // import decl containing the best match
impIndex = -1 // spec index in impDecl containing the best match
isThirdPartyPath = isThirdParty(path)
)
for i, decl := range f.Decls {
gen, ok := decl.(*ast.GenDecl)
if ok && gen.Tok == token.IMPORT {
lastImport = i
// Do not add to import "C", to avoid disrupting the
// association with its doc comment, breaking cgo.
if declImports(gen, "C") {
continue
}
// Match an empty import decl if that's all that is available.
if len(gen.Specs) == 0 && bestMatch == -1 {
impDecl = gen
}
// Compute longest shared prefix with imports in this group and find best
// matched import spec.
// 1. Always prefer import spec with longest shared prefix.
// 2. While match length is 0,
// - for stdlib package: prefer first import spec.
// - for third party package: prefer first third party import spec.
// We cannot use last import spec as best match for third party package
// because grouped imports are usually placed last by goimports -local
// flag.
// See issue #19190.
seenAnyThirdParty := false
for j, spec := range gen.Specs {
impspec := spec.(*ast.ImportSpec)
p := importPath(impspec)
n := matchLen(p, path)
if n > bestMatch || (bestMatch == 0 && !seenAnyThirdParty && isThirdPartyPath) {
bestMatch = n
impDecl = gen
impIndex = j
}
seenAnyThirdParty = seenAnyThirdParty || isThirdParty(p)
}
}
}
// If no import decl found, add one after the last import.
if impDecl == nil {
impDecl = &ast.GenDecl{
Tok: token.IMPORT,
}
if lastImport >= 0 {
impDecl.TokPos = f.Decls[lastImport].End()
} else {
// There are no existing imports.
// Our new import, preceded by a blank line, goes after the package declaration
// and after the comment, if any, that starts on the same line as the
// package declaration.
impDecl.TokPos = f.Package
file := fset.File(f.Package)
pkgLine := file.Line(f.Package)
for _, c := range f.Comments {
if file.Line(c.Pos()) > pkgLine {
break
}
// +2 for a blank line
impDecl.TokPos = c.End() + 2
}
}
f.Decls = append(f.Decls, nil)
copy(f.Decls[lastImport+2:], f.Decls[lastImport+1:])
f.Decls[lastImport+1] = impDecl
}
// Insert new import at insertAt.
insertAt := 0
if impIndex >= 0 {
// insert after the found import
insertAt = impIndex + 1
}
impDecl.Specs = append(impDecl.Specs, nil)
copy(impDecl.Specs[insertAt+1:], impDecl.Specs[insertAt:])
impDecl.Specs[insertAt] = newImport
pos := impDecl.Pos()
if insertAt > 0 {
// If there is a comment after an existing import, preserve the comment
// position by adding the new import after the comment.
if spec, ok := impDecl.Specs[insertAt-1].(*ast.ImportSpec); ok && spec.Comment != nil {
pos = spec.Comment.End()
} else {
// Assign same position as the previous import,
// so that the sorter sees it as being in the same block.
pos = impDecl.Specs[insertAt-1].Pos()
}
}
if newImport.Name != nil {
newImport.Name.NamePos = pos
}
updateBasicLitPos(newImport.Path, pos)
newImport.EndPos = pos
// Clean up parens. impDecl contains at least one spec.
if len(impDecl.Specs) == 1 {
// Remove unneeded parens.
impDecl.Lparen = token.NoPos
} else if !impDecl.Lparen.IsValid() {
// impDecl needs parens added.
impDecl.Lparen = impDecl.Specs[0].Pos()
}
f.Imports = append(f.Imports, newImport)
if len(f.Decls) <= 1 {
return true
}
// Merge all the import declarations into the first one.
var first *ast.GenDecl
for i := 0; i < len(f.Decls); i++ {
decl := f.Decls[i]
gen, ok := decl.(*ast.GenDecl)
if !ok || gen.Tok != token.IMPORT || declImports(gen, "C") {
continue
}
if first == nil {
first = gen
continue // Don't touch the first one.
}
// We now know there is more than one package in this import
// declaration. Ensure that it ends up parenthesized.
first.Lparen = first.Pos()
// Move the imports of the other import declaration to the first one.
for _, spec := range gen.Specs {
updateBasicLitPos(spec.(*ast.ImportSpec).Path, first.Pos())
first.Specs = append(first.Specs, spec)
}
f.Decls = slices.Delete(f.Decls, i, i+1)
i--
}
return true
}
func isThirdParty(importPath string) bool {
// Third party package import path usually contains "." (".com", ".org", ...)
// This logic is taken from golang.org/x/tools/imports package.
return strings.Contains(importPath, ".")
}
// DeleteImport deletes the import path from the file f, if present.
// If there are duplicate import declarations, all matching ones are deleted.
func DeleteImport(fset *token.FileSet, f *ast.File, path string) (deleted bool) {
return DeleteNamedImport(fset, f, "", path)
}
// DeleteNamedImport deletes the import with the given name and path from the file f, if present.
// If there are duplicate import declarations, all matching ones are deleted.
func DeleteNamedImport(fset *token.FileSet, f *ast.File, name, path string) (deleted bool) {
var (
delspecs = make(map[*ast.ImportSpec]bool)
delcomments = make(map[*ast.CommentGroup]bool)
)
// Find the import nodes that import path, if any.
for i := 0; i < len(f.Decls); i++ {
gen, ok := f.Decls[i].(*ast.GenDecl)
if !ok || gen.Tok != token.IMPORT {
continue
}
for j := 0; j < len(gen.Specs); j++ {
impspec := gen.Specs[j].(*ast.ImportSpec)
if importName(impspec) != name || importPath(impspec) != path {
continue
}
// We found an import spec that imports path.
// Delete it.
delspecs[impspec] = true
deleted = true
gen.Specs = slices.Delete(gen.Specs, j, j+1)
// If this was the last import spec in this decl,
// delete the decl, too.
if len(gen.Specs) == 0 {
f.Decls = slices.Delete(f.Decls, i, i+1)
i--
break
} else if len(gen.Specs) == 1 {
if impspec.Doc != nil {
delcomments[impspec.Doc] = true
}
if impspec.Comment != nil {
delcomments[impspec.Comment] = true
}
for _, cg := range f.Comments {
// Found comment on the same line as the import spec.
if cg.End() < impspec.Pos() && fset.Position(cg.End()).Line == fset.Position(impspec.Pos()).Line {
delcomments[cg] = true
break
}
}
spec := gen.Specs[0].(*ast.ImportSpec)
// Move the documentation right after the import decl.
if spec.Doc != nil {
for fset.Position(gen.TokPos).Line+1 < fset.Position(spec.Doc.Pos()).Line {
fset.File(gen.TokPos).MergeLine(fset.Position(gen.TokPos).Line)
}
}
for _, cg := range f.Comments {
if cg.End() < spec.Pos() && fset.Position(cg.End()).Line == fset.Position(spec.Pos()).Line {
for fset.Position(gen.TokPos).Line+1 < fset.Position(spec.Pos()).Line {
fset.File(gen.TokPos).MergeLine(fset.Position(gen.TokPos).Line)
}
break
}
}
}
if j > 0 {
lastImpspec := gen.Specs[j-1].(*ast.ImportSpec)
lastLine := fset.PositionFor(lastImpspec.Path.ValuePos, false).Line
line := fset.PositionFor(impspec.Path.ValuePos, false).Line
// We deleted an entry but now there may be
// a blank line-sized hole where the import was.
if line-lastLine > 1 || !gen.Rparen.IsValid() {
// There was a blank line immediately preceding the deleted import,
// so there's no need to close the hole. The right parenthesis is
// invalid after AddImport to an import statement without parenthesis.
// Do nothing.
} else if line != fset.File(gen.Rparen).LineCount() {
// There was no blank line. Close the hole.
fset.File(gen.Rparen).MergeLine(line)
}
}
j--
}
}
// Delete imports from f.Imports.
before := len(f.Imports)
f.Imports = slices.DeleteFunc(f.Imports, func(imp *ast.ImportSpec) bool {
_, ok := delspecs[imp]
return ok
})
if len(f.Imports)+len(delspecs) != before {
// This can happen when the AST is invalid (i.e. imports differ between f.Decls and f.Imports).
panic(fmt.Sprintf("deleted specs from Decls but not Imports: %v", delspecs))
}
// Delete comments from f.Comments.
f.Comments = slices.DeleteFunc(f.Comments, func(cg *ast.CommentGroup) bool {
_, ok := delcomments[cg]
return ok
})
return
}
// RewriteImport rewrites any import of path oldPath to path newPath.
func RewriteImport(fset *token.FileSet, f *ast.File, oldPath, newPath string) (rewrote bool) {
for _, imp := range f.Imports {
if importPath(imp) == oldPath {
rewrote = true
// record old End, because the default is to compute
// it using the length of imp.Path.Value.
imp.EndPos = imp.End()
imp.Path.Value = strconv.Quote(newPath)
}
}
return
}
// UsesImport reports whether a given import is used.
// The provided File must have been parsed with syntactic object resolution
// (not using go/parser.SkipObjectResolution).
func UsesImport(f *ast.File, path string) (used bool) {
if f.Scope == nil {
panic("file f was not parsed with syntactic object resolution")
}
spec := importSpec(f, path)
if spec == nil {
return
}
name := spec.Name.String()
switch name {
case "":
// If the package name is not explicitly specified,
// make an educated guess. This is not guaranteed to be correct.
lastSlash := strings.LastIndex(path, "/")
if lastSlash == -1 {
name = path
} else {
name = path[lastSlash+1:]
}
case "_", ".":
// Not sure if this import is used - err on the side of caution.
return true
}
ast.Walk(visitFn(func(n ast.Node) {
sel, ok := n.(*ast.SelectorExpr)
if ok && isTopName(sel.X, name) {
used = true
}
}), f)
return
}
type visitFn func(node ast.Node)
func (fn visitFn) Visit(node ast.Node) ast.Visitor {
fn(node)
return fn
}
// imports reports whether f has an import with the specified name and path.
func imports(f *ast.File, name, path string) bool {
for _, s := range f.Imports {
if importName(s) == name && importPath(s) == path {
return true
}
}
return false
}
// importSpec returns the import spec if f imports path,
// or nil otherwise.
func importSpec(f *ast.File, path string) *ast.ImportSpec {
for _, s := range f.Imports {
if importPath(s) == path {
return s
}
}
return nil
}
// importName returns the name of s,
// or "" if the import is not named.
func importName(s *ast.ImportSpec) string {
if s.Name == nil {
return ""
}
return s.Name.Name
}
// importPath returns the unquoted import path of s,
// or "" if the path is not properly quoted.
func importPath(s *ast.ImportSpec) string {
t, err := strconv.Unquote(s.Path.Value)
if err != nil {
return ""
}
return t
}
// declImports reports whether gen contains an import of path.
func declImports(gen *ast.GenDecl, path string) bool {
if gen.Tok != token.IMPORT {
return false
}
for _, spec := range gen.Specs {
impspec := spec.(*ast.ImportSpec)
if importPath(impspec) == path {
return true
}
}
return false
}
// matchLen returns the length of the longest path segment prefix shared by x and y.
func matchLen(x, y string) int {
n := 0
for i := 0; i < len(x) && i < len(y) && x[i] == y[i]; i++ {
if x[i] == '/' {
n++
}
}
return n
}
// isTopName returns true if n is a top-level unresolved identifier with the given name.
func isTopName(n ast.Expr, name string) bool {
id, ok := n.(*ast.Ident)
return ok && id.Name == name && id.Obj == nil
}
// Imports returns the file imports grouped by paragraph.
func Imports(fset *token.FileSet, f *ast.File) [][]*ast.ImportSpec {
var groups [][]*ast.ImportSpec
for _, decl := range f.Decls {
genDecl, ok := decl.(*ast.GenDecl)
if !ok || genDecl.Tok != token.IMPORT {
break
}
group := []*ast.ImportSpec{}
var lastLine int
for _, spec := range genDecl.Specs {
importSpec := spec.(*ast.ImportSpec)
pos := importSpec.Path.ValuePos
line := fset.Position(pos).Line
if lastLine > 0 && pos > 0 && line-lastLine > 1 {
groups = append(groups, group)
group = []*ast.ImportSpec{}
}
group = append(group, importSpec)
lastLine = line
}
groups = append(groups, group)
}
return groups
}
// updateBasicLitPos updates lit.Pos,
// ensuring that lit.End (if set) is displaced by the same amount.
// (See https://go.dev/issue/76395.)
func updateBasicLitPos(lit *ast.BasicLit, pos token.Pos) {
len := lit.End() - lit.Pos()
lit.ValuePos = pos
// TODO(adonovan): after go1.26, simplify to:
// lit.ValueEnd = pos + len
v := reflect.ValueOf(lit).Elem().FieldByName("ValueEnd")
if v.IsValid() && v.Int() != 0 {
v.SetInt(int64(pos + len))
}
}
================================================
FILE: go/ast/astutil/imports_test.go
================================================
// Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package astutil
import (
"bytes"
"go/ast"
"go/format"
"go/parser"
"go/token"
"reflect"
"strconv"
"testing"
)
var fset = token.NewFileSet()
func parse(t *testing.T, name, in string) *ast.File {
file, err := parser.ParseFile(fset, name, in, parser.ParseComments)
if err != nil {
t.Fatalf("%s parse: %v", name, err)
}
return file
}
func print(t *testing.T, name string, f *ast.File) string {
var buf bytes.Buffer
if err := format.Node(&buf, fset, f); err != nil {
t.Fatalf("%s gofmt: %v", name, err)
}
return buf.String()
}
type test struct {
name string
renamedPkg string
pkg string
in string
out string
unchanged bool // Expect added/deleted return value to be false.
}
var addTests = []test{
{
name: "leave os alone",
pkg: "os",
in: `package main
import (
"os"
)
`,
out: `package main
import (
"os"
)
`,
unchanged: true,
},
{
name: "import.1",
pkg: "os",
in: `package main
`,
out: `package main
import "os"
`,
},
{
name: "import.2",
pkg: "os",
in: `package main
// Comment
import "C"
`,
out: `package main
// Comment
import "C"
import "os"
`,
},
{
name: "import.3",
pkg: "os",
in: `package main
// Comment
import "C"
import (
"io"
"utf8"
)
`,
out: `package main
// Comment
import "C"
import (
"io"
"os"
"utf8"
)
`,
},
{
name: "import.17",
pkg: "x/y/z",
in: `package main
// Comment
import "C"
import (
"a"
"b"
"x/w"
"d/f"
)
`,
out: `package main
// Comment
import "C"
import (
"a"
"b"
"x/w"
"x/y/z"
"d/f"
)
`,
},
{
name: "issue #19190",
pkg: "x.org/y/z",
in: `package main
// Comment
import "C"
import (
"bytes"
"os"
"d.com/f"
)
`,
out: `package main
// Comment
import "C"
import (
"bytes"
"os"
"d.com/f"
"x.org/y/z"
)
`,
},
{
name: "issue #19190 with existing grouped import packages",
pkg: "x.org/y/z",
in: `package main
// Comment
import "C"
import (
"bytes"
"os"
"c.com/f"
"d.com/f"
"y.com/a"
"y.com/b"
"y.com/c"
)
`,
out: `package main
// Comment
import "C"
import (
"bytes"
"os"
"c.com/f"
"d.com/f"
"x.org/y/z"
"y.com/a"
"y.com/b"
"y.com/c"
)
`,
},
{
name: "issue #19190 - match score is still respected",
pkg: "y.org/c",
in: `package main
import (
"x.org/a"
"y.org/b"
)
`,
out: `package main
import (
"x.org/a"
"y.org/b"
"y.org/c"
)
`,
},
{
name: "import into singular group",
pkg: "bytes",
in: `package main
import "os"
`,
out: `package main
import (
"bytes"
"os"
)
`,
},
{
name: "import into singular group with comment",
pkg: "bytes",
in: `package main
import /* why */ /* comment here? */ "os"
`,
out: `package main
import /* why */ /* comment here? */ (
"bytes"
"os"
)
`,
},
{
name: "import into group with leading comment",
pkg: "strings",
in: `package main
import (
// comment before bytes
"bytes"
"os"
)
`,
out: `package main
import (
// comment before bytes
"bytes"
"os"
"strings"
)
`,
},
{
name: "",
renamedPkg: "fmtpkg",
pkg: "fmt",
in: `package main
import "os"
`,
out: `package main
import (
fmtpkg "fmt"
"os"
)
`,
},
{
name: "struct comment",
pkg: "time",
in: `package main
// This is a comment before a struct.
type T struct {
t time.Time
}
`,
out: `package main
import "time"
// This is a comment before a struct.
type T struct {
t time.Time
}
`,
},
{
name: "issue 8729 import C",
pkg: "time",
in: `package main
import "C"
// comment
type T time.Time
`,
out: `package main
import "C"
import "time"
// comment
type T time.Time
`,
},
{
name: "issue 8729 empty import",
pkg: "time",
in: `package main
import ()
// comment
type T time.Time
`,
out: `package main
import "time"
// comment
type T time.Time
`,
},
{
name: "issue 8729 comment on package line",
pkg: "time",
in: `package main // comment
type T time.Time
`,
out: `package main // comment
import "time"
type T time.Time
`,
},
{
name: "issue 8729 comment after package",
pkg: "time",
in: `package main
// comment
type T time.Time
`,
out: `package main
import "time"
// comment
type T time.Time
`,
},
{
name: "issue 8729 comment before and on package line",
pkg: "time",
in: `// comment before
package main // comment on
type T time.Time
`,
out: `// comment before
package main // comment on
import "time"
type T time.Time
`,
},
// Issue 9961: Match prefixes using path segments rather than bytes
{
name: "issue 9961",
pkg: "regexp",
in: `package main
import (
"flag"
"testing"
"rsc.io/p"
)
`,
out: `package main
import (
"flag"
"regexp"
"testing"
"rsc.io/p"
)
`,
},
// Issue 10337: Preserve comment position
{
name: "issue 10337",
pkg: "fmt",
in: `package main
import (
"bytes" // a
"log" // c
)
`,
out: `package main
import (
"bytes" // a
"fmt"
"log" // c
)
`,
},
{
name: "issue 10337 new import at the start",
pkg: "bytes",
in: `package main
import (
"fmt" // b
"log" // c
)
`,
out: `package main
import (
"bytes"
"fmt" // b
"log" // c
)
`,
},
{
name: "issue 10337 new import at the end",
pkg: "log",
in: `package main
import (
"bytes" // a
"fmt" // b
)
`,
out: `package main
import (
"bytes" // a
"fmt" // b
"log"
)
`,
},
// Issue 14075: Merge import declarations
{
name: "issue 14075",
pkg: "bufio",
in: `package main
import "bytes"
import "fmt"
`,
out: `package main
import (
"bufio"
"bytes"
"fmt"
)
`,
},
{
name: "issue 14075 update position",
pkg: "bufio",
in: `package main
import "bytes"
import (
"fmt"
)
`,
out: `package main
import (
"bufio"
"bytes"
"fmt"
)
`,
},
{
name: `issue 14075 ignore import "C"`,
pkg: "bufio",
in: `package main
// Comment
import "C"
import "bytes"
import "fmt"
`,
out: `package main
// Comment
import "C"
import (
"bufio"
"bytes"
"fmt"
)
`,
},
{
name: `issue 14075 ignore adjacent import "C"`,
pkg: "bufio",
in: `package main
// Comment
import "C"
import "fmt"
`,
out: `package main
// Comment
import "C"
import (
"bufio"
"fmt"
)
`,
},
{
name: `issue 14075 ignore adjacent import "C" (without factored import)`,
pkg: "bufio",
in: `package main
// Comment
import "C"
import "fmt"
`,
out: `package main
// Comment
import "C"
import (
"bufio"
"fmt"
)
`,
},
{
name: `issue 14075 ignore single import "C"`,
pkg: "bufio",
in: `package main
// Comment
import "C"
`,
out: `package main
// Comment
import "C"
import "bufio"
`,
},
{
name: `issue 17212 several single-import lines with shared prefix ending in a slash`,
pkg: "net/http",
in: `package main
import "bufio"
import "net/url"
`,
out: `package main
import (
"bufio"
"net/http"
"net/url"
)
`,
},
{
name: `issue 17212 block imports lines with shared prefix ending in a slash`,
pkg: "net/http",
in: `package main
import (
"bufio"
"net/url"
)
`,
out: `package main
import (
"bufio"
"net/http"
"net/url"
)
`,
},
{
name: `issue 17213 many single-import lines`,
pkg: "fmt",
in: `package main
import "bufio"
import "bytes"
import "errors"
`,
out: `package main
import (
"bufio"
"bytes"
"errors"
"fmt"
)
`,
},
// Issue 28605: Add specified import, even if that import path is imported under another name
{
name: "issue 28605 add unnamed path",
renamedPkg: "",
pkg: "path",
in: `package main
import (
. "path"
_ "path"
pathpkg "path"
)
`,
out: `package main
import (
"path"
. "path"
_ "path"
pathpkg "path"
)
`,
},
{
name: "issue 28605 add pathpkg-renamed path",
renamedPkg: "pathpkg",
pkg: "path",
in: `package main
import (
"path"
. "path"
_ "path"
)
`,
out: `package main
import (
"path"
. "path"
_ "path"
pathpkg "path"
)
`,
},
{
name: "issue 28605 add blank identifier path",
renamedPkg: "_",
pkg: "path",
in: `package main
import (
"path"
. "path"
pathpkg "path"
)
`,
out: `package main
import (
"path"
. "path"
_ "path"
pathpkg "path"
)
`,
},
{
name: "issue 28605 add dot import path",
renamedPkg: ".",
pkg: "path",
in: `package main
import (
"path"
_ "path"
pathpkg "path"
)
`,
out: `package main
import (
"path"
. "path"
_ "path"
pathpkg "path"
)
`,
},
{
name: "duplicate import declarations, add existing one",
renamedPkg: "f",
pkg: "fmt",
in: `package main
import "fmt"
import "fmt"
import f "fmt"
import f "fmt"
`,
out: `package main
import "fmt"
import "fmt"
import f "fmt"
import f "fmt"
`,
unchanged: true,
},
}
func TestAddImport(t *testing.T) {
for _, test := range addTests {
file := parse(t, test.name, test.in)
var before bytes.Buffer
ast.Fprint(&before, fset, file, nil)
added := AddNamedImport(fset, file, test.renamedPkg, test.pkg)
if got := print(t, test.name, file); got != test.out {
t.Errorf("first run: %s:\ngot: %s\nwant: %s", test.name, got, test.out)
var after bytes.Buffer
ast.Fprint(&after, fset, file, nil)
t.Logf("AST before:\n%s\nAST after:\n%s\n", before.String(), after.String())
}
if got, want := added, !test.unchanged; got != want {
t.Errorf("first run: %s: added = %v, want %v", test.name, got, want)
}
// AddNamedImport should be idempotent. Verify that by calling it again,
// expecting no change to the AST, and the returned added value to always be false.
added = AddNamedImport(fset, file, test.renamedPkg, test.pkg)
if got := print(t, test.name, file); got != test.out {
t.Errorf("second run: %s:\ngot: %s\nwant: %s", test.name, got, test.out)
}
if got, want := added, false; got != want {
t.Errorf("second run: %s: added = %v, want %v", test.name, got, want)
}
}
}
func TestDoubleAddImport(t *testing.T) {
file := parse(t, "doubleimport", "package main\n")
AddImport(fset, file, "os")
AddImport(fset, file, "bytes")
want := `package main
import (
"bytes"
"os"
)
`
if got := print(t, "doubleimport", file); got != want {
t.Errorf("got: %s\nwant: %s", got, want)
}
}
func TestDoubleAddNamedImport(t *testing.T) {
file := parse(t, "doublenamedimport", "package main\n")
AddNamedImport(fset, file, "o", "os")
AddNamedImport(fset, file, "i", "io")
want := `package main
import (
i "io"
o "os"
)
`
if got := print(t, "doublenamedimport", file); got != want {
t.Errorf("got: %s\nwant: %s", got, want)
}
}
// Part of issue 8729.
func TestDoubleAddImportWithDeclComment(t *testing.T) {
file := parse(t, "doubleimport", `package main
import (
)
// comment
type I int
`)
// The AddImport order here matters.
AddImport(fset, file, "golang.org/x/tools/go/ast/astutil")
AddImport(fset, file, "os")
want := `package main
import (
"golang.org/x/tools/go/ast/astutil"
"os"
)
// comment
type I int
`
if got := print(t, "doubleimport_with_decl_comment", file); got != want {
t.Errorf("got: %s\nwant: %s", got, want)
}
}
var deleteTests = []test{
{
name: "import.4",
pkg: "os",
in: `package main
import (
"os"
)
`,
out: `package main
`,
},
{
name: "import.5",
pkg: "os",
in: `package main
// Comment
import "C"
import "os"
`,
out: `package main
// Comment
import "C"
`,
},
{
name: "import.6",
pkg: "os",
in: `package main
// Comment
import "C"
import (
"io"
"os"
"utf8"
)
`,
out: `package main
// Comment
import "C"
import (
"io"
"utf8"
)
`,
},
{
name: "import.7",
pkg: "io",
in: `package main
import (
"io" // a
"os" // b
"utf8" // c
)
`,
out: `package main
import (
// a
"os" // b
"utf8" // c
)
`,
},
{
name: "import.8",
pkg: "os",
in: `package main
import (
"io" // a
"os" // b
"utf8" // c
)
`,
out: `package main
import (
"io" // a
// b
"utf8" // c
)
`,
},
{
name: "import.9",
pkg: "utf8",
in: `package main
import (
"io" // a
"os" // b
"utf8" // c
)
`,
out: `package main
import (
"io" // a
"os" // b
// c
)
`,
},
{
name: "import.10",
pkg: "io",
in: `package main
import (
"io"
"os"
"utf8"
)
`,
out: `package main
import (
"os"
"utf8"
)
`,
},
{
name: "import.11",
pkg: "os",
in: `package main
import (
"io"
"os"
"utf8"
)
`,
out: `package main
import (
"io"
"utf8"
)
`,
},
{
name: "import.12",
pkg: "utf8",
in: `package main
import (
"io"
"os"
"utf8"
)
`,
out: `package main
import (
"io"
"os"
)
`,
},
{
name: "handle.raw.quote.imports",
pkg: "os",
in: "package main\n\nimport `os`",
out: `package main
`,
},
{
name: "import.13",
pkg: "io",
in: `package main
import (
"fmt"
"io"
"os"
"utf8"
"go/format"
)
`,
out: `package main
import (
"fmt"
"os"
"utf8"
"go/format"
)
`,
},
{
name: "import.14",
pkg: "io",
in: `package main
import (
"fmt" // a
"io" // b
"os" // c
"utf8" // d
"go/format" // e
)
`,
out: `package main
import (
"fmt" // a
// b
"os" // c
"utf8" // d
"go/format" // e
)
`,
},
{
name: "import.15",
pkg: "double",
in: `package main
import (
"double"
"double"
)
`,
out: `package main
`,
},
{
name: "import.16",
pkg: "bubble",
in: `package main
import (
"toil"
"bubble"
"bubble"
"trouble"
)
`,
out: `package main
import (
"toil"
"trouble"
)
`,
},
{
name: "import.17",
pkg: "quad",
in: `package main
import (
"quad"
"quad"
)
import (
"quad"
"quad"
)
`,
out: `package main
`,
},
{
name: "import.18",
renamedPkg: "x",
pkg: "fmt",
in: `package main
import (
"fmt"
x "fmt"
)
`,
out: `package main
import (
"fmt"
)
`,
},
{
name: "import.18",
renamedPkg: "x",
pkg: "fmt",
in: `package main
import x "fmt"
import y "fmt"
`,
out: `package main
import y "fmt"
`,
},
// Issue #15432, #18051
{
name: "import.19",
pkg: "fmt",
in: `package main
import (
"fmt"
// Some comment.
"io"
)`,
out: `package main
import (
// Some comment.
"io"
)
`,
},
{
name: "import.20",
pkg: "fmt",
in: `package main
import (
"fmt"
// Some
// comment.
"io"
)`,
out: `package main
import (
// Some
// comment.
"io"
)
`,
},
{
name: "import.21",
pkg: "fmt",
in: `package main
import (
"fmt"
/*
Some
comment.
*/
"io"
)`,
out: `package main
import (
/*
Some
comment.
*/
"io"
)
`,
},
{
name: "import.22",
pkg: "fmt",
in: `package main
import (
/* Some */
// comment.
"io"
"fmt"
)`,
out: `package main
import (
/* Some */
// comment.
"io"
)
`,
},
{
name: "import.23",
pkg: "fmt",
in: `package main
import (
// comment 1
"fmt"
// comment 2
"io"
)`,
out: `package main
import (
// comment 2
"io"
)
`,
},
{
name: "import.24",
pkg: "fmt",
in: `package main
import (
"fmt" // comment 1
"io" // comment 2
)`,
out: `package main
import (
"io" // comment 2
)
`,
},
{
name: "import.25",
pkg: "fmt",
in: `package main
import (
"fmt"
/* comment */ "io"
)`,
out: `package main
import (
/* comment */ "io"
)
`,
},
{
name: "import.26",
pkg: "fmt",
in: `package main
import (
"fmt"
"io" /* comment */
)`,
out: `package main
import (
"io" /* comment */
)
`,
},
{
name: "import.27",
pkg: "fmt",
in: `package main
import (
"fmt" /* comment */
"io"
)`,
out: `package main
import (
"io"
)
`,
},
{
name: "import.28",
pkg: "fmt",
in: `package main
import (
/* comment */ "fmt"
"io"
)`,
out: `package main
import (
"io"
)
`,
},
{
name: "import.29",
pkg: "fmt",
in: `package main
// comment 1
import (
"fmt"
"io" // comment 2
)`,
out: `package main
// comment 1
import (
"io" // comment 2
)
`,
},
{
name: "import.30",
pkg: "fmt",
in: `package main
// comment 1
import (
"fmt" // comment 2
"io"
)`,
out: `package main
// comment 1
import (
"io"
)
`,
},
{
name: "import.31",
pkg: "fmt",
in: `package main
// comment 1
import (
"fmt"
/* comment 2 */ "io"
)`,
out: `package main
// comment 1
import (
/* comment 2 */ "io"
)
`,
},
{
name: "import.32",
pkg: "fmt",
renamedPkg: "f",
in: `package main
// comment 1
import (
f "fmt"
/* comment 2 */ i "io"
)`,
out: `package main
// comment 1
import (
/* comment 2 */ i "io"
)
`,
},
{
name: "import.33",
pkg: "fmt",
renamedPkg: "f",
in: `package main
// comment 1
import (
/* comment 2 */ f "fmt"
i "io"
)`,
out: `package main
// comment 1
import (
i "io"
)
`,
},
{
name: "import.34",
pkg: "fmt",
renamedPkg: "f",
in: `package main
// comment 1
import (
f "fmt" /* comment 2 */
i "io"
)`,
out: `package main
// comment 1
import (
i "io"
)
`,
},
{
name: "import.35",
pkg: "fmt",
in: `package main
// comment 1
import (
"fmt"
// comment 2
"io"
)`,
out: `package main
// comment 1
import (
// comment 2
"io"
)
`,
},
{
name: "import.36",
pkg: "fmt",
in: `package main
/* comment 1 */
import (
"fmt"
/* comment 2 */
"io"
)`,
out: `package main
/* comment 1 */
import (
/* comment 2 */
"io"
)
`,
},
// Issue 20229: MergeLine panic on weird input
{
name: "import.37",
pkg: "io",
in: `package main
import("_"
"io")`,
out: `package main
import (
"_"
)
`,
},
// Issue 28605: Delete specified import, even if that import path is imported under another name
{
name: "import.38",
renamedPkg: "",
pkg: "path",
in: `package main
import (
"path"
. "path"
_ "path"
pathpkg "path"
)
`,
out: `package main
import (
. "path"
_ "path"
pathpkg "path"
)
`,
},
{
name: "import.39",
renamedPkg: "pathpkg",
pkg: "path",
in: `package main
import (
"path"
. "path"
_ "path"
pathpkg "path"
)
`,
out: `package main
import (
"path"
. "path"
_ "path"
)
`,
},
{
name: "import.40",
renamedPkg: "_",
pkg: "path",
in: `package main
import (
"path"
. "path"
_ "path"
pathpkg "path"
)
`,
out: `package main
import (
"path"
. "path"
pathpkg "path"
)
`,
},
{
name: "import.41",
renamedPkg: ".",
pkg: "path",
in: `package main
import (
"path"
. "path"
_ "path"
pathpkg "path"
)
`,
out: `package main
import (
"path"
_ "path"
pathpkg "path"
)
`,
},
// Duplicate import declarations, all matching ones are deleted.
{
name: "import.42",
renamedPkg: "f",
pkg: "fmt",
in: `package main
import "fmt"
import "fmt"
import f "fmt"
import f "fmt"
`,
out: `package main
import "fmt"
import "fmt"
`,
},
{
name: "import.43",
renamedPkg: "x",
pkg: "fmt",
in: `package main
import "fmt"
import "fmt"
import f "fmt"
import f "fmt"
`,
out: `package main
import "fmt"
import "fmt"
import f "fmt"
import f "fmt"
`,
unchanged: true,
},
// this test panics without PositionFor in DeleteNamedImport
{
name: "import.44",
pkg: "foo.com/other/v3",
renamedPkg: "",
in: `package main
//line mah.go:600
import (
"foo.com/a.thing"
"foo.com/surprise"
"foo.com/v1"
"foo.com/other/v2"
"foo.com/other/v3"
)
`,
out: `package main
//line mah.go:600
import (
"foo.com/a.thing"
"foo.com/other/v2"
"foo.com/surprise"
"foo.com/v1"
)
`,
},
}
func TestDeleteImport(t *testing.T) {
for _, test := range deleteTests {
file := parse(t, test.name, test.in)
var before bytes.Buffer
ast.Fprint(&before, fset, file, nil)
deleted := DeleteNamedImport(fset, file, test.renamedPkg, test.pkg)
if got := print(t, test.name, file); got != test.out {
t.Errorf("first run: %s:\ngot: %s\nwant: %s", test.name, got, test.out)
var after bytes.Buffer
ast.Fprint(&after, fset, file, nil)
t.Logf("AST before:\n%s\nAST after:\n%s\n", before.String(), after.String())
}
if got, want := deleted, !test.unchanged; got != want {
t.Errorf("first run: %s: deleted = %v, want %v", test.name, got, want)
}
// DeleteNamedImport should be idempotent. Verify that by calling it again,
// expecting no change to the AST, and the returned deleted value to always be false.
deleted = DeleteNamedImport(fset, file, test.renamedPkg, test.pkg)
if got := print(t, test.name, file); got != test.out {
t.Errorf("second run: %s:\ngot: %s\nwant: %s", test.name, got, test.out)
}
if got, want := deleted, false; got != want {
t.Errorf("second run: %s: deleted = %v, want %v", test.name, got, want)
}
}
}
func TestDeleteImportAfterAddImport(t *testing.T) {
file := parse(t, "test", `package main
import "os"
`)
if got, want := AddImport(fset, file, "fmt"), true; got != want {
t.Errorf("AddImport: got: %v, want: %v", got, want)
}
if got, want := DeleteImport(fset, file, "fmt"), true; got != want {
t.Errorf("DeleteImport: got: %v, want: %v", got, want)
}
}
type rewriteTest struct {
name string
srcPkg string
dstPkg string
in string
out string
}
var rewriteTests = []rewriteTest{
{
name: "import.13",
srcPkg: "utf8",
dstPkg: "encoding/utf8",
in: `package main
import (
"io"
"os"
"utf8" // thanks ken
)
`,
out: `package main
import (
"encoding/utf8" // thanks ken
"io"
"os"
)
`,
},
{
name: "import.14",
srcPkg: "asn1",
dstPkg: "encoding/asn1",
in: `package main
import (
"asn1"
"crypto"
"crypto/rsa"
_ "crypto/sha1"
"crypto/x509"
"crypto/x509/pkix"
"time"
)
var x = 1
`,
out: `package main
import (
"crypto"
"crypto/rsa"
_ "crypto/sha1"
"crypto/x509"
"crypto/x509/pkix"
"encoding/asn1"
"time"
)
var x = 1
`,
},
{
name: "import.15",
srcPkg: "url",
dstPkg: "net/url",
in: `package main
import (
"bufio"
"net"
"path"
"url"
)
var x = 1 // comment on x, not on url
`,
out: `package main
import (
"bufio"
"net"
"net/url"
"path"
)
var x = 1 // comment on x, not on url
`,
},
{
name: "import.16",
srcPkg: "http",
dstPkg: "net/http",
in: `package main
import (
"flag"
"http"
"log"
"text/template"
)
var addr = flag.String("addr", ":1718", "http service address") // Q=17, R=18
`,
out: `package main
import (
"flag"
"log"
"net/http"
"text/template"
)
var addr = flag.String("addr", ":1718", "http service address") // Q=17, R=18
`,
},
}
func TestRewriteImport(t *testing.T) {
for _, test := range rewriteTests {
file := parse(t, test.name, test.in)
RewriteImport(fset, file, test.srcPkg, test.dstPkg)
if got := print(t, test.name, file); got != test.out {
t.Errorf("%s:\ngot: %s\nwant: %s", test.name, got, test.out)
}
}
}
var importsTests = []struct {
name string
in string
want [][]string
}{
{
name: "no packages",
in: `package foo
`,
want: nil,
},
{
name: "one group",
in: `package foo
import (
"fmt"
"testing"
)
`,
want: [][]string{{"fmt", "testing"}},
},
{
name: "four groups",
in: `package foo
import "C"
import (
"fmt"
"testing"
"appengine"
"myproject/mylib1"
"myproject/mylib2"
)
`,
want: [][]string{
{"C"},
{"fmt", "testing"},
{"appengine"},
{"myproject/mylib1", "myproject/mylib2"},
},
},
{
name: "multiple factored groups",
in: `package foo
import (
"fmt"
"testing"
"appengine"
)
import (
"reflect"
"bytes"
)
`,
want: [][]string{
{"fmt", "testing"},
{"appengine"},
{"reflect"},
{"bytes"},
},
},
}
func unquote(s string) string {
res, err := strconv.Unquote(s)
if err != nil {
return "could_not_unquote"
}
return res
}
func TestImports(t *testing.T) {
fset := token.NewFileSet()
for _, test := range importsTests {
f, err := parser.ParseFile(fset, "test.go", test.in, 0)
if err != nil {
t.Errorf("%s: %v", test.name, err)
continue
}
var got [][]string
for _, group := range Imports(fset, f) {
var b []string
for _, spec := range group {
b = append(b, unquote(spec.Path.Value))
}
got = append(got, b)
}
if !reflect.DeepEqual(got, test.want) {
t.Errorf("Imports(%s)=%v, want %v", test.name, got, test.want)
}
}
}
var usesImportTests = []struct {
name string
path string
in string
want bool
}{
{
name: "no packages",
path: "io",
in: `package foo
`,
want: false,
},
{
name: "import.1",
path: "io",
in: `package foo
import "io"
var _ io.Writer
`,
want: true,
},
{
name: "import.2",
path: "io",
in: `package foo
import "io"
`,
want: false,
},
{
name: "import.3",
path: "io",
in: `package foo
import "io"
var io = 42
`,
want: false,
},
{
name: "import.4",
path: "io",
in: `package foo
import i "io"
var _ i.Writer
`,
want: true,
},
{
name: "import.5",
path: "io",
in: `package foo
import i "io"
`,
want: false,
},
{
name: "import.6",
path: "io",
in: `package foo
import i "io"
var i = 42
var io = 42
`,
want: false,
},
{
name: "import.7",
path: "encoding/json",
in: `package foo
import "encoding/json"
var _ json.Encoder
`,
want: true,
},
{
name: "import.8",
path: "encoding/json",
in: `package foo
import "encoding/json"
`,
want: false,
},
{
name: "import.9",
path: "encoding/json",
in: `package foo
import "encoding/json"
var json = 42
`,
want: false,
},
{
name: "import.10",
path: "encoding/json",
in: `package foo
import j "encoding/json"
var _ j.Encoder
`,
want: true,
},
{
name: "import.11",
path: "encoding/json",
in: `package foo
import j "encoding/json"
`,
want: false,
},
{
name: "import.12",
path: "encoding/json",
in: `package foo
import j "encoding/json"
var j = 42
var json = 42
`,
want: false,
},
{
name: "import.13",
path: "io",
in: `package foo
import _ "io"
`,
want: true,
},
{
name: "import.14",
path: "io",
in: `package foo
import . "io"
`,
want: true,
},
}
func TestUsesImport(t *testing.T) {
fset := token.NewFileSet()
for _, test := range usesImportTests {
f, err := parser.ParseFile(fset, "test.go", test.in, 0)
if err != nil {
t.Errorf("%s: %v", test.name, err)
continue
}
got := UsesImport(f, test.path)
if got != test.want {
t.Errorf("UsesImport(%s)=%v, want %v", test.name, got, test.want)
}
}
}
================================================
FILE: go/ast/astutil/rewrite.go
================================================
// Copyright 2017 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package astutil
import (
"fmt"
"go/ast"
"reflect"
"sort"
)
// An ApplyFunc is invoked by Apply for each node n, even if n is nil,
// before and/or after the node's children, using a Cursor describing
// the current node and providing operations on it.
//
// The return value of ApplyFunc controls the syntax tree traversal.
// See Apply for details.
type ApplyFunc func(*Cursor) bool
// Apply traverses a syntax tree recursively, starting with root,
// and calling pre and post for each node as described below.
// Apply returns the syntax tree, possibly modified.
//
// If pre is not nil, it is called for each node before the node's
// children are traversed (pre-order). If pre returns false, no
// children are traversed, and post is not called for that node.
//
// If post is not nil, and a prior call of pre didn't return false,
// post is called for each node after its children are traversed
// (post-order). If post returns false, traversal is terminated and
// Apply returns immediately.
//
// Only fields that refer to AST nodes are considered children;
// i.e., token.Pos, Scopes, Objects, and fields of basic types
// (strings, etc.) are ignored.
//
// Children are traversed in the order in which they appear in the
// respective node's struct definition. A package's files are
// traversed in the filenames' alphabetical order.
func Apply(root ast.Node, pre, post ApplyFunc) (result ast.Node) {
parent := &struct{ ast.Node }{root}
defer func() {
if r := recover(); r != nil && r != abort {
panic(r)
}
result = parent.Node
}()
a := &application{pre: pre, post: post}
a.apply(parent, "Node", nil, root)
return
}
var abort = new(int) // singleton, to signal termination of Apply
// A Cursor describes a node encountered during Apply.
// Information about the node and its parent is available
// from the Node, Parent, Name, and Index methods.
//
// If p is a variable of type and value of the current parent node
// c.Parent(), and f is the field identifier with name c.Name(),
// the following invariants hold:
//
// p.f == c.Node() if c.Index() < 0
// p.f[c.Index()] == c.Node() if c.Index() >= 0
//
// The methods Replace, Delete, InsertBefore, and InsertAfter
// can be used to change the AST without disrupting Apply.
//
// This type is not to be confused with [inspector.Cursor] from
// package [golang.org/x/tools/go/ast/inspector], which provides
// stateless navigation of immutable syntax trees.
type Cursor struct {
parent ast.Node
name string
iter *iterator // valid if non-nil
node ast.Node
}
// Node returns the current Node.
func (c *Cursor) Node() ast.Node { return c.node }
// Parent returns the parent of the current Node.
func (c *Cursor) Parent() ast.Node { return c.parent }
// Name returns the name of the parent Node field that contains the current Node.
// If the parent is a *ast.Package and the current Node is a *ast.File, Name returns
// the filename for the current Node.
func (c *Cursor) Name() string { return c.name }
// Index reports the index >= 0 of the current Node in the slice of Nodes that
// contains it, or a value < 0 if the current Node is not part of a slice.
// The index of the current node changes if InsertBefore is called while
// processing the current node.
func (c *Cursor) Index() int {
if c.iter != nil {
return c.iter.index
}
return -1
}
// field returns the current node's parent field value.
func (c *Cursor) field() reflect.Value {
return reflect.Indirect(reflect.ValueOf(c.parent)).FieldByName(c.name)
}
// Replace replaces the current Node with n.
// The replacement node is not walked by Apply.
func (c *Cursor) Replace(n ast.Node) {
if _, ok := c.node.(*ast.File); ok {
file, ok := n.(*ast.File)
if !ok {
panic("attempt to replace *ast.File with non-*ast.File")
}
c.parent.(*ast.Package).Files[c.name] = file
return
}
v := c.field()
if i := c.Index(); i >= 0 {
v = v.Index(i)
}
v.Set(reflect.ValueOf(n))
}
// Delete deletes the current Node from its containing slice.
// If the current Node is not part of a slice, Delete panics.
// As a special case, if the current node is a package file,
// Delete removes it from the package's Files map.
func (c *Cursor) Delete() {
if _, ok := c.node.(*ast.File); ok {
delete(c.parent.(*ast.Package).Files, c.name)
return
}
i := c.Index()
if i < 0 {
panic("Delete node not contained in slice")
}
v := c.field()
l := v.Len()
reflect.Copy(v.Slice(i, l), v.Slice(i+1, l))
v.Index(l - 1).Set(reflect.Zero(v.Type().Elem()))
v.SetLen(l - 1)
c.iter.step--
}
// InsertAfter inserts n after the current Node in its containing slice.
// If the current Node is not part of a slice, InsertAfter panics.
// Apply does not walk n.
func (c *Cursor) InsertAfter(n ast.Node) {
i := c.Index()
if i < 0 {
panic("InsertAfter node not contained in slice")
}
v := c.field()
v.Set(reflect.Append(v, reflect.Zero(v.Type().Elem())))
l := v.Len()
reflect.Copy(v.Slice(i+2, l), v.Slice(i+1, l))
v.Index(i + 1).Set(reflect.ValueOf(n))
c.iter.step++
}
// InsertBefore inserts n before the current Node in its containing slice.
// If the current Node is not part of a slice, InsertBefore panics.
// Apply will not walk n.
func (c *Cursor) InsertBefore(n ast.Node) {
i := c.Index()
if i < 0 {
panic("InsertBefore node not contained in slice")
}
v := c.field()
v.Set(reflect.Append(v, reflect.Zero(v.Type().Elem())))
l := v.Len()
reflect.Copy(v.Slice(i+1, l), v.Slice(i, l))
v.Index(i).Set(reflect.ValueOf(n))
c.iter.index++
}
// application carries all the shared data so we can pass it around cheaply.
type application struct {
pre, post ApplyFunc
cursor Cursor
iter iterator
}
func (a *application) apply(parent ast.Node, name string, iter *iterator, n ast.Node) {
// convert typed nil into untyped nil
if v := reflect.ValueOf(n); v.Kind() == reflect.Pointer && v.IsNil() {
n = nil
}
// avoid heap-allocating a new cursor for each apply call; reuse a.cursor instead
saved := a.cursor
a.cursor.parent = parent
a.cursor.name = name
a.cursor.iter = iter
a.cursor.node = n
if a.pre != nil && !a.pre(&a.cursor) {
a.cursor = saved
return
}
// walk children
// (the order of the cases matches the order of the corresponding node types in go/ast)
switch n := n.(type) {
case nil:
// nothing to do
// Comments and fields
case *ast.Comment:
// nothing to do
case *ast.CommentGroup:
if n != nil {
a.applyList(n, "List")
}
case *ast.Field:
a.apply(n, "Doc", nil, n.Doc)
a.applyList(n, "Names")
a.apply(n, "Type", nil, n.Type)
a.apply(n, "Tag", nil, n.Tag)
a.apply(n, "Comment", nil, n.Comment)
case *ast.FieldList:
a.applyList(n, "List")
// Expressions
case *ast.BadExpr, *ast.Ident, *ast.BasicLit:
// nothing to do
case *ast.Ellipsis:
a.apply(n, "Elt", nil, n.Elt)
case *ast.FuncLit:
a.apply(n, "Type", nil, n.Type)
a.apply(n, "Body", nil, n.Body)
case *ast.CompositeLit:
a.apply(n, "Type", nil, n.Type)
a.applyList(n, "Elts")
case *ast.ParenExpr:
a.apply(n, "X", nil, n.X)
case *ast.SelectorExpr:
a.apply(n, "X", nil, n.X)
a.apply(n, "Sel", nil, n.Sel)
case *ast.IndexExpr:
a.apply(n, "X", nil, n.X)
a.apply(n, "Index", nil, n.Index)
case *ast.IndexListExpr:
a.apply(n, "X", nil, n.X)
a.applyList(n, "Indices")
case *ast.SliceExpr:
a.apply(n, "X", nil, n.X)
a.apply(n, "Low", nil, n.Low)
a.apply(n, "High", nil, n.High)
a.apply(n, "Max", nil, n.Max)
case *ast.TypeAssertExpr:
a.apply(n, "X", nil, n.X)
a.apply(n, "Type", nil, n.Type)
case *ast.CallExpr:
a.apply(n, "Fun", nil, n.Fun)
a.applyList(n, "Args")
case *ast.StarExpr:
a.apply(n, "X", nil, n.X)
case *ast.UnaryExpr:
a.apply(n, "X", nil, n.X)
case *ast.BinaryExpr:
a.apply(n, "X", nil, n.X)
a.apply(n, "Y", nil, n.Y)
case *ast.KeyValueExpr:
a.apply(n, "Key", nil, n.Key)
a.apply(n, "Value", nil, n.Value)
// Types
case *ast.ArrayType:
a.apply(n, "Len", nil, n.Len)
a.apply(n, "Elt", nil, n.Elt)
case *ast.StructType:
a.apply(n, "Fields", nil, n.Fields)
case *ast.FuncType:
if tparams := n.TypeParams; tparams != nil {
a.apply(n, "TypeParams", nil, tparams)
}
a.apply(n, "Params", nil, n.Params)
a.apply(n, "Results", nil, n.Results)
case *ast.InterfaceType:
a.apply(n, "Methods", nil, n.Methods)
case *ast.MapType:
a.apply(n, "Key", nil, n.Key)
a.apply(n, "Value", nil, n.Value)
case *ast.ChanType:
a.apply(n, "Value", nil, n.Value)
// Statements
case *ast.BadStmt:
// nothing to do
case *ast.DeclStmt:
a.apply(n, "Decl", nil, n.Decl)
case *ast.EmptyStmt:
// nothing to do
case *ast.LabeledStmt:
a.apply(n, "Label", nil, n.Label)
a.apply(n, "Stmt", nil, n.Stmt)
case *ast.ExprStmt:
a.apply(n, "X", nil, n.X)
case *ast.SendStmt:
a.apply(n, "Chan", nil, n.Chan)
a.apply(n, "Value", nil, n.Value)
case *ast.IncDecStmt:
a.apply(n, "X", nil, n.X)
case *ast.AssignStmt:
a.applyList(n, "Lhs")
a.applyList(n, "Rhs")
case *ast.GoStmt:
a.apply(n, "Call", nil, n.Call)
case *ast.DeferStmt:
a.apply(n, "Call", nil, n.Call)
case *ast.ReturnStmt:
a.applyList(n, "Results")
case *ast.BranchStmt:
a.apply(n, "Label", nil, n.Label)
case *ast.BlockStmt:
a.applyList(n, "List")
case *ast.IfStmt:
a.apply(n, "Init", nil, n.Init)
a.apply(n, "Cond", nil, n.Cond)
a.apply(n, "Body", nil, n.Body)
a.apply(n, "Else", nil, n.Else)
case *ast.CaseClause:
a.applyList(n, "List")
a.applyList(n, "Body")
case *ast.SwitchStmt:
a.apply(n, "Init", nil, n.Init)
a.apply(n, "Tag", nil, n.Tag)
a.apply(n, "Body", nil, n.Body)
case *ast.TypeSwitchStmt:
a.apply(n, "Init", nil, n.Init)
a.apply(n, "Assign", nil, n.Assign)
a.apply(n, "Body", nil, n.Body)
case *ast.CommClause:
a.apply(n, "Comm", nil, n.Comm)
a.applyList(n, "Body")
case *ast.SelectStmt:
a.apply(n, "Body", nil, n.Body)
case *ast.ForStmt:
a.apply(n, "Init", nil, n.Init)
a.apply(n, "Cond", nil, n.Cond)
a.apply(n, "Post", nil, n.Post)
a.apply(n, "Body", nil, n.Body)
case *ast.RangeStmt:
a.apply(n, "Key", nil, n.Key)
a.apply(n, "Value", nil, n.Value)
a.apply(n, "X", nil, n.X)
a.apply(n, "Body", nil, n.Body)
// Declarations
case *ast.ImportSpec:
a.apply(n, "Doc", nil, n.Doc)
a.apply(n, "Name", nil, n.Name)
a.apply(n, "Path", nil, n.Path)
a.apply(n, "Comment", nil, n.Comment)
case *ast.ValueSpec:
a.apply(n, "Doc", nil, n.Doc)
a.applyList(n, "Names")
a.apply(n, "Type", nil, n.Type)
a.applyList(n, "Values")
a.apply(n, "Comment", nil, n.Comment)
case *ast.TypeSpec:
a.apply(n, "Doc", nil, n.Doc)
a.apply(n, "Name", nil, n.Name)
if tparams := n.TypeParams; tparams != nil {
a.apply(n, "TypeParams", nil, tparams)
}
a.apply(n, "Type", nil, n.Type)
a.apply(n, "Comment", nil, n.Comment)
case *ast.BadDecl:
// nothing to do
case *ast.GenDecl:
a.apply(n, "Doc", nil, n.Doc)
a.applyList(n, "Specs")
case *ast.FuncDecl:
a.apply(n, "Doc", nil, n.Doc)
a.apply(n, "Recv", nil, n.Recv)
a.apply(n, "Name", nil, n.Name)
a.apply(n, "Type", nil, n.Type)
a.apply(n, "Body", nil, n.Body)
// Files and packages
case *ast.File:
a.apply(n, "Doc", nil, n.Doc)
a.apply(n, "Name", nil, n.Name)
a.applyList(n, "Decls")
// Don't walk n.Comments; they have either been walked already if
// they are Doc comments, or they can be easily walked explicitly.
case *ast.Package:
// collect and sort names for reproducible behavior
var names []string
for name := range n.Files {
names = append(names, name)
}
sort.Strings(names)
for _, name := range names {
a.apply(n, name, nil, n.Files[name])
}
default:
panic(fmt.Sprintf("Apply: unexpected node type %T", n))
}
if a.post != nil && !a.post(&a.cursor) {
panic(abort)
}
a.cursor = saved
}
// An iterator controls iteration over a slice of nodes.
type iterator struct {
index, step int
}
func (a *application) applyList(parent ast.Node, name string) {
// avoid heap-allocating a new iterator for each applyList call; reuse a.iter instead
saved := a.iter
a.iter.index = 0
for {
// must reload parent.name each time, since cursor modifications might change it
v := reflect.Indirect(reflect.ValueOf(parent)).FieldByName(name)
if a.iter.index >= v.Len() {
break
}
// element x may be nil in a bad AST - be cautious
var x ast.Node
if e := v.Index(a.iter.index); e.IsValid() {
x = e.Interface().(ast.Node)
}
a.iter.step = 1
a.apply(parent, name, &a.iter, x)
a.iter.index += a.iter.step
}
a.iter = saved
}
================================================
FILE: go/ast/astutil/rewrite_test.go
================================================
// Copyright 2017 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package astutil_test
import (
"bytes"
"go/ast"
"go/format"
"go/parser"
"go/token"
"testing"
"golang.org/x/tools/go/ast/astutil"
)
type rewriteTest struct {
name string
orig, want string
pre, post astutil.ApplyFunc
}
var rewriteTests = []rewriteTest{
{name: "nop", orig: "package p\n", want: "package p\n"},
{name: "replace",
orig: `package p
var x int
`,
want: `package p
var t T
`,
post: func(c *astutil.Cursor) bool {
if _, ok := c.Node().(*ast.ValueSpec); ok {
c.Replace(valspec("t", "T"))
return false
}
return true
},
},
{name: "set doc strings",
orig: `package p
const z = 0
type T struct{}
var x int
`,
want: `package p
// a foo is a foo
const z = 0
// a foo is a foo
type T struct{}
// a foo is a foo
var x int
`,
post: func(c *astutil.Cursor) bool {
if _, ok := c.Parent().(*ast.GenDecl); ok && c.Name() == "Doc" && c.Node() == nil {
c.Replace(&ast.CommentGroup{List: []*ast.Comment{{Text: "// a foo is a foo"}}})
}
return true
},
},
{name: "insert names",
orig: `package p
const a = 1
`,
want: `package p
const a, b, c = 1, 2, 3
`,
pre: func(c *astutil.Cursor) bool {
if _, ok := c.Parent().(*ast.ValueSpec); ok {
switch c.Name() {
case "Names":
c.InsertAfter(ast.NewIdent("c"))
c.InsertAfter(ast.NewIdent("b"))
case "Values":
c.InsertAfter(&ast.BasicLit{Kind: token.INT, Value: "3"})
c.InsertAfter(&ast.BasicLit{Kind: token.INT, Value: "2"})
}
}
return true
},
},
{name: "insert",
orig: `package p
var (
x int
y int
)
`,
want: `package p
var before1 int
var before2 int
var (
x int
y int
)
var after2 int
var after1 int
`,
pre: func(c *astutil.Cursor) bool {
if _, ok := c.Node().(*ast.GenDecl); ok {
c.InsertBefore(vardecl("before1", "int"))
c.InsertAfter(vardecl("after1", "int"))
c.InsertAfter(vardecl("after2", "int"))
c.InsertBefore(vardecl("before2", "int"))
}
return true
},
},
{name: "delete",
orig: `package p
var x int
var y int
var z int
`,
want: `package p
var y int
var z int
`,
pre: func(c *astutil.Cursor) bool {
n := c.Node()
if d, ok := n.(*ast.GenDecl); ok && d.Specs[0].(*ast.ValueSpec).Names[0].Name == "x" {
c.Delete()
}
return true
},
},
{name: "insertafter-delete",
orig: `package p
var x int
var y int
var z int
`,
want: `package p
var x1 int
var y int
var z int
`,
pre: func(c *astutil.Cursor) bool {
n := c.Node()
if d, ok := n.(*ast.GenDecl); ok && d.Specs[0].(*ast.ValueSpec).Names[0].Name == "x" {
c.InsertAfter(vardecl("x1", "int"))
c.Delete()
}
return true
},
},
{name: "delete-insertafter",
orig: `package p
var x int
var y int
var z int
`,
want: `package p
var y int
var x1 int
var z int
`,
pre: func(c *astutil.Cursor) bool {
n := c.Node()
if d, ok := n.(*ast.GenDecl); ok && d.Specs[0].(*ast.ValueSpec).Names[0].Name == "x" {
c.Delete()
// The cursor is now effectively atop the 'var y int' node.
c.InsertAfter(vardecl("x1", "int"))
}
return true
},
},
{
name: "replace",
orig: `package p
type T[P1, P2 any] int
type R T[int, string]
func F[Q1 any](q Q1) {}
`,
// TODO: note how the rewrite adds a trailing comma in "func F".
// Is that a bug in the test, or in astutil.Apply?
want: `package p
type S[R1, P2 any] int32
type R S[int32, string]
func F[X1 any](q X1,) {}
`,
post: func(c *astutil.Cursor) bool {
if ident, ok := c.Node().(*ast.Ident); ok {
switch ident.Name {
case "int":
c.Replace(ast.NewIdent("int32"))
case "T":
c.Replace(ast.NewIdent("S"))
case "P1":
c.Replace(ast.NewIdent("R1"))
case "Q1":
c.Replace(ast.NewIdent("X1"))
}
}
return true
},
},
}
func valspec(name, typ string) *ast.ValueSpec {
return &ast.ValueSpec{Names: []*ast.Ident{ast.NewIdent(name)},
Type: ast.NewIdent(typ),
}
}
func vardecl(name, typ string) *ast.GenDecl {
return &ast.GenDecl{
Tok: token.VAR,
Specs: []ast.Spec{valspec(name, typ)},
}
}
func TestRewrite(t *testing.T) {
t.Run("*", func(t *testing.T) {
for _, test := range rewriteTests {
t.Run(test.name, func(t *testing.T) {
t.Parallel()
fset := token.NewFileSet()
f, err := parser.ParseFile(fset, test.name, test.orig, parser.ParseComments)
if err != nil {
t.Fatal(err)
}
n := astutil.Apply(f, test.pre, test.post)
var buf bytes.Buffer
if err := format.Node(&buf, fset, n); err != nil {
t.Fatal(err)
}
got := buf.String()
if got != test.want {
t.Errorf("got:\n\n%s\nwant:\n\n%s\n", got, test.want)
}
})
}
})
}
var sink ast.Node
func BenchmarkRewrite(b *testing.B) {
for _, test := range rewriteTests {
b.Run(test.name, func(b *testing.B) {
for b.Loop() {
b.StopTimer()
fset := token.NewFileSet()
f, err := parser.ParseFile(fset, test.name, test.orig, parser.ParseComments)
if err != nil {
b.Fatal(err)
}
b.StartTimer()
sink = astutil.Apply(f, test.pre, test.post)
}
})
}
}
================================================
FILE: go/ast/astutil/util.go
================================================
// Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package astutil
import "go/ast"
// Unparen returns e with any enclosing parentheses stripped.
// Deprecated: use [ast.Unparen].
//
//go:fix inline
func Unparen(e ast.Expr) ast.Expr { return ast.Unparen(e) }
================================================
FILE: go/ast/edge/edge.go
================================================
// Copyright 2025 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package edge defines identifiers for each field of an ast.Node
// struct type that refers to another Node.
package edge
import (
"fmt"
"go/ast"
"reflect"
)
// A Kind describes a field of an ast.Node struct.
type Kind uint8
// String returns a description of the edge kind.
func (k Kind) String() string {
if k == Invalid {
return ""
}
info := fieldInfos[k]
return fmt.Sprintf("%v.%s", info.nodeType.Elem().Name(), info.name)
}
// NodeType returns the pointer-to-struct type of the ast.Node implementation.
func (k Kind) NodeType() reflect.Type { return fieldInfos[k].nodeType }
// FieldName returns the name of the field.
func (k Kind) FieldName() string { return fieldInfos[k].name }
// FieldType returns the declared type of the field.
func (k Kind) FieldType() reflect.Type { return fieldInfos[k].fieldType }
// Get returns the direct child of n identified by (k, idx).
// n's type must match k.NodeType().
// idx must be a valid slice index, or -1 for a non-slice.
func (k Kind) Get(n ast.Node, idx int) ast.Node {
if k.NodeType() != reflect.TypeOf(n) {
panic(fmt.Sprintf("%v.Get(%T): invalid node type", k, n))
}
v := reflect.ValueOf(n).Elem().Field(fieldInfos[k].index)
if idx != -1 {
v = v.Index(idx) // asserts valid index
} else {
// (The type assertion below asserts that v is not a slice.)
}
return v.Interface().(ast.Node) // may be nil
}
const (
Invalid Kind = iota // for nodes at the root of the traversal
// Kinds are sorted alphabetically.
// Numbering is not stable.
// Each is named Type_Field, where Type is the
// ast.Node struct type and Field is the name of the field
ArrayType_Elt
ArrayType_Len
AssignStmt_Lhs
AssignStmt_Rhs
BinaryExpr_X
BinaryExpr_Y
BlockStmt_List
BranchStmt_Label
CallExpr_Args
CallExpr_Fun
CaseClause_Body
CaseClause_List
ChanType_Value
CommClause_Body
CommClause_Comm
CommentGroup_List
CompositeLit_Elts
CompositeLit_Type
DeclStmt_Decl
DeferStmt_Call
Ellipsis_Elt
ExprStmt_X
FieldList_List
Field_Comment
Field_Doc
Field_Names
Field_Tag
Field_Type
File_Decls
File_Doc
File_Name
ForStmt_Body
ForStmt_Cond
ForStmt_Init
ForStmt_Post
FuncDecl_Body
FuncDecl_Doc
FuncDecl_Name
FuncDecl_Recv
FuncDecl_Type
FuncLit_Body
FuncLit_Type
FuncType_Params
FuncType_Results
FuncType_TypeParams
GenDecl_Doc
GenDecl_Specs
GoStmt_Call
IfStmt_Body
IfStmt_Cond
IfStmt_Else
IfStmt_Init
ImportSpec_Comment
ImportSpec_Doc
ImportSpec_Name
ImportSpec_Path
IncDecStmt_X
IndexExpr_Index
IndexExpr_X
IndexListExpr_Indices
IndexListExpr_X
InterfaceType_Methods
KeyValueExpr_Key
KeyValueExpr_Value
LabeledStmt_Label
LabeledStmt_Stmt
MapType_Key
MapType_Value
ParenExpr_X
RangeStmt_Body
RangeStmt_Key
RangeStmt_Value
RangeStmt_X
ReturnStmt_Results
SelectStmt_Body
SelectorExpr_Sel
SelectorExpr_X
SendStmt_Chan
SendStmt_Value
SliceExpr_High
SliceExpr_Low
SliceExpr_Max
SliceExpr_X
StarExpr_X
StructType_Fields
SwitchStmt_Body
SwitchStmt_Init
SwitchStmt_Tag
TypeAssertExpr_Type
TypeAssertExpr_X
TypeSpec_Comment
TypeSpec_Doc
TypeSpec_Name
TypeSpec_Type
TypeSpec_TypeParams
TypeSwitchStmt_Assign
TypeSwitchStmt_Body
TypeSwitchStmt_Init
UnaryExpr_X
ValueSpec_Comment
ValueSpec_Doc
ValueSpec_Names
ValueSpec_Type
ValueSpec_Values
maxKind
)
// Assert that the encoding fits in 7 bits,
// as the inspector relies on this.
// (We are currently at 104.)
var _ = [1 << 7]struct{}{}[maxKind]
type fieldInfo struct {
nodeType reflect.Type // pointer-to-struct type of ast.Node implementation
name string
index int
fieldType reflect.Type
}
func info[N ast.Node](fieldName string) fieldInfo {
nodePtrType := reflect.TypeFor[N]()
f, ok := nodePtrType.Elem().FieldByName(fieldName)
if !ok {
panic(fieldName)
}
return fieldInfo{nodePtrType, fieldName, f.Index[0], f.Type}
}
var fieldInfos = [...]fieldInfo{
Invalid: {},
ArrayType_Elt: info[*ast.ArrayType]("Elt"),
ArrayType_Len: info[*ast.ArrayType]("Len"),
AssignStmt_Lhs: info[*ast.AssignStmt]("Lhs"),
AssignStmt_Rhs: info[*ast.AssignStmt]("Rhs"),
BinaryExpr_X: info[*ast.BinaryExpr]("X"),
BinaryExpr_Y: info[*ast.BinaryExpr]("Y"),
BlockStmt_List: info[*ast.BlockStmt]("List"),
BranchStmt_Label: info[*ast.BranchStmt]("Label"),
CallExpr_Args: info[*ast.CallExpr]("Args"),
CallExpr_Fun: info[*ast.CallExpr]("Fun"),
CaseClause_Body: info[*ast.CaseClause]("Body"),
CaseClause_List: info[*ast.CaseClause]("List"),
ChanType_Value: info[*ast.ChanType]("Value"),
CommClause_Body: info[*ast.CommClause]("Body"),
CommClause_Comm: info[*ast.CommClause]("Comm"),
CommentGroup_List: info[*ast.CommentGroup]("List"),
CompositeLit_Elts: info[*ast.CompositeLit]("Elts"),
CompositeLit_Type: info[*ast.CompositeLit]("Type"),
DeclStmt_Decl: info[*ast.DeclStmt]("Decl"),
DeferStmt_Call: info[*ast.DeferStmt]("Call"),
Ellipsis_Elt: info[*ast.Ellipsis]("Elt"),
ExprStmt_X: info[*ast.ExprStmt]("X"),
FieldList_List: info[*ast.FieldList]("List"),
Field_Comment: info[*ast.Field]("Comment"),
Field_Doc: info[*ast.Field]("Doc"),
Field_Names: info[*ast.Field]("Names"),
Field_Tag: info[*ast.Field]("Tag"),
Field_Type: info[*ast.Field]("Type"),
File_Decls: info[*ast.File]("Decls"),
File_Doc: info[*ast.File]("Doc"),
File_Name: info[*ast.File]("Name"),
ForStmt_Body: info[*ast.ForStmt]("Body"),
ForStmt_Cond: info[*ast.ForStmt]("Cond"),
ForStmt_Init: info[*ast.ForStmt]("Init"),
ForStmt_Post: info[*ast.ForStmt]("Post"),
FuncDecl_Body: info[*ast.FuncDecl]("Body"),
FuncDecl_Doc: info[*ast.FuncDecl]("Doc"),
FuncDecl_Name: info[*ast.FuncDecl]("Name"),
FuncDecl_Recv: info[*ast.FuncDecl]("Recv"),
FuncDecl_Type: info[*ast.FuncDecl]("Type"),
FuncLit_Body: info[*ast.FuncLit]("Body"),
FuncLit_Type: info[*ast.FuncLit]("Type"),
FuncType_Params: info[*ast.FuncType]("Params"),
FuncType_Results: info[*ast.FuncType]("Results"),
FuncType_TypeParams: info[*ast.FuncType]("TypeParams"),
GenDecl_Doc: info[*ast.GenDecl]("Doc"),
GenDecl_Specs: info[*ast.GenDecl]("Specs"),
GoStmt_Call: info[*ast.GoStmt]("Call"),
IfStmt_Body: info[*ast.IfStmt]("Body"),
IfStmt_Cond: info[*ast.IfStmt]("Cond"),
IfStmt_Else: info[*ast.IfStmt]("Else"),
IfStmt_Init: info[*ast.IfStmt]("Init"),
ImportSpec_Comment: info[*ast.ImportSpec]("Comment"),
ImportSpec_Doc: info[*ast.ImportSpec]("Doc"),
ImportSpec_Name: info[*ast.ImportSpec]("Name"),
ImportSpec_Path: info[*ast.ImportSpec]("Path"),
IncDecStmt_X: info[*ast.IncDecStmt]("X"),
IndexExpr_Index: info[*ast.IndexExpr]("Index"),
IndexExpr_X: info[*ast.IndexExpr]("X"),
IndexListExpr_Indices: info[*ast.IndexListExpr]("Indices"),
IndexListExpr_X: info[*ast.IndexListExpr]("X"),
InterfaceType_Methods: info[*ast.InterfaceType]("Methods"),
KeyValueExpr_Key: info[*ast.KeyValueExpr]("Key"),
KeyValueExpr_Value: info[*ast.KeyValueExpr]("Value"),
LabeledStmt_Label: info[*ast.LabeledStmt]("Label"),
LabeledStmt_Stmt: info[*ast.LabeledStmt]("Stmt"),
MapType_Key: info[*ast.MapType]("Key"),
MapType_Value: info[*ast.MapType]("Value"),
ParenExpr_X: info[*ast.ParenExpr]("X"),
RangeStmt_Body: info[*ast.RangeStmt]("Body"),
RangeStmt_Key: info[*ast.RangeStmt]("Key"),
RangeStmt_Value: info[*ast.RangeStmt]("Value"),
RangeStmt_X: info[*ast.RangeStmt]("X"),
ReturnStmt_Results: info[*ast.ReturnStmt]("Results"),
SelectStmt_Body: info[*ast.SelectStmt]("Body"),
SelectorExpr_Sel: info[*ast.SelectorExpr]("Sel"),
SelectorExpr_X: info[*ast.SelectorExpr]("X"),
SendStmt_Chan: info[*ast.SendStmt]("Chan"),
SendStmt_Value: info[*ast.SendStmt]("Value"),
SliceExpr_High: info[*ast.SliceExpr]("High"),
SliceExpr_Low: info[*ast.SliceExpr]("Low"),
SliceExpr_Max: info[*ast.SliceExpr]("Max"),
SliceExpr_X: info[*ast.SliceExpr]("X"),
StarExpr_X: info[*ast.StarExpr]("X"),
StructType_Fields: info[*ast.StructType]("Fields"),
SwitchStmt_Body: info[*ast.SwitchStmt]("Body"),
SwitchStmt_Init: info[*ast.SwitchStmt]("Init"),
SwitchStmt_Tag: info[*ast.SwitchStmt]("Tag"),
TypeAssertExpr_Type: info[*ast.TypeAssertExpr]("Type"),
TypeAssertExpr_X: info[*ast.TypeAssertExpr]("X"),
TypeSpec_Comment: info[*ast.TypeSpec]("Comment"),
TypeSpec_Doc: info[*ast.TypeSpec]("Doc"),
TypeSpec_Name: info[*ast.TypeSpec]("Name"),
TypeSpec_Type: info[*ast.TypeSpec]("Type"),
TypeSpec_TypeParams: info[*ast.TypeSpec]("TypeParams"),
TypeSwitchStmt_Assign: info[*ast.TypeSwitchStmt]("Assign"),
TypeSwitchStmt_Body: info[*ast.TypeSwitchStmt]("Body"),
TypeSwitchStmt_Init: info[*ast.TypeSwitchStmt]("Init"),
UnaryExpr_X: info[*ast.UnaryExpr]("X"),
ValueSpec_Comment: info[*ast.ValueSpec]("Comment"),
ValueSpec_Doc: info[*ast.ValueSpec]("Doc"),
ValueSpec_Names: info[*ast.ValueSpec]("Names"),
ValueSpec_Type: info[*ast.ValueSpec]("Type"),
ValueSpec_Values: info[*ast.ValueSpec]("Values"),
}
================================================
FILE: go/ast/inspector/cursor.go
================================================
// Copyright 2025 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package inspector
import (
"fmt"
"go/ast"
"go/token"
"iter"
"reflect"
"golang.org/x/tools/go/ast/edge"
)
// A Cursor represents an [ast.Node]. It is immutable.
//
// Two Cursors compare equal if they represent the same node.
//
// The zero value of Cursor is not valid.
//
// Call [Inspector.Root] to obtain a cursor for the virtual root node
// of the traversal. This is the sole valid cursor for which [Cursor.Node]
// returns nil.
//
// Use the following methods to navigate efficiently around the tree:
// - for ancestors, use [Cursor.Parent] and [Cursor.Enclosing];
// - for children, use [Cursor.Child], [Cursor.Children],
// [Cursor.FirstChild], and [Cursor.LastChild];
// - for siblings, use [Cursor.PrevSibling] and [Cursor.NextSibling];
// - for descendants, use [Cursor.FindByPos], [Cursor.FindNode],
// [Cursor.Inspect], and [Cursor.Preorder].
//
// Use the [Cursor.ChildAt] and [Cursor.ParentEdge] methods for
// information about the edges in a tree: which field (and slice
// element) of the parent node holds the child.
type Cursor struct {
in *Inspector
index int32 // index of push node; -1 for virtual root node
}
// Root returns a valid cursor for the virtual root node,
// whose children are the files provided to [New].
//
// Its [Cursor.Node] method return nil.
func (in *Inspector) Root() Cursor {
return Cursor{in, -1}
}
// At returns the cursor at the specified index in the traversal,
// which must have been obtained from [Cursor.Index] on a Cursor
// belonging to the same Inspector (see [Cursor.Inspector]).
func (in *Inspector) At(index int32) Cursor {
if index < 0 {
panic("negative index")
}
if int(index) >= len(in.events) {
panic("index out of range for this inspector")
}
if in.events[index].index < index {
panic("invalid index") // (a push, not a pop)
}
return Cursor{in, index}
}
// Valid reports whether the cursor is valid.
// The zero value of cursor is invalid.
// Unless otherwise documented, it is not safe to call
// any other method on an invalid cursor.
func (c Cursor) Valid() bool {
return c.in != nil
}
// Inspector returns the cursor's Inspector.
// It returns nil if the Cursor is not valid.
func (c Cursor) Inspector() *Inspector { return c.in }
// Index returns the index of this cursor position within the package.
//
// Clients should not assume anything about the numeric Index value
// except that it increases monotonically throughout the traversal.
// It is provided for use with [Inspector.At].
//
// Index must not be called on the Root node.
func (c Cursor) Index() int32 {
if c.index < 0 {
panic("Index called on Root node")
}
return c.index
}
// Node returns the node at the current cursor position,
// or nil for the cursor returned by [Inspector.Root].
func (c Cursor) Node() ast.Node {
if c.index < 0 {
return nil
}
return c.in.events[c.index].node
}
// String returns information about the cursor's node, if any.
func (c Cursor) String() string {
if !c.Valid() {
return "(invalid)"
}
if c.index < 0 {
return "(root)"
}
return reflect.TypeOf(c.Node()).String()
}
// indices return the [start, end) half-open interval of event indices.
func (c Cursor) indices() (int32, int32) {
if c.index < 0 {
return 0, int32(len(c.in.events)) // root: all events
} else {
return c.index, c.in.events[c.index].index + 1 // just one subtree
}
}
// Preorder returns an iterator over the nodes of the subtree
// represented by c in depth-first order. Each node in the sequence is
// represented by a Cursor that allows access to the Node, but may
// also be used to start a new traversal, or to obtain the stack of
// nodes enclosing the cursor.
//
// The traversal sequence is determined by [ast.Inspect]. The types
// argument, if non-empty, enables type-based filtering of events. The
// function f if is called only for nodes whose type matches an
// element of the types slice.
//
// If you need control over descent into subtrees,
// or need both pre- and post-order notifications, use [Cursor.Inspect]
func (c Cursor) Preorder(types ...ast.Node) iter.Seq[Cursor] {
mask := maskOf(types)
return func(yield func(Cursor) bool) {
events := c.in.events
for i, limit := c.indices(); i < limit; {
ev := events[i]
if ev.index > i { // push?
if ev.typ&mask != 0 && !yield(Cursor{c.in, i}) {
break
}
pop := ev.index
if events[pop].typ&mask == 0 {
// Subtree does not contain types: skip.
i = pop + 1
continue
}
}
i++
}
}
}
// Inspect visits the nodes of the subtree represented by c in
// depth-first order. It calls f(n) for each node n before it
// visits n's children. If f returns true, Inspect invokes f
// recursively for each of the non-nil children of the node.
//
// Each node is represented by a Cursor that allows access to the
// Node, but may also be used to start a new traversal, or to obtain
// the stack of nodes enclosing the cursor.
//
// The complete traversal sequence is determined by [ast.Inspect].
// The types argument, if non-empty, enables type-based filtering of
// events. The function f if is called only for nodes whose type
// matches an element of the types slice.
func (c Cursor) Inspect(types []ast.Node, f func(c Cursor) (descend bool)) {
mask := maskOf(types)
events := c.in.events
for i, limit := c.indices(); i < limit; {
ev := events[i]
if ev.index > i {
// push
pop := ev.index
if ev.typ&mask != 0 && !f(Cursor{c.in, i}) ||
events[pop].typ&mask == 0 {
// The user opted not to descend, or the
// subtree does not contain types:
// skip past the pop.
i = pop + 1
continue
}
}
i++
}
}
// Enclosing returns an iterator over the nodes enclosing the current
// current node, starting with the Cursor itself.
//
// Enclosing must not be called on the Root node (whose [Cursor.Node] returns nil).
//
// The types argument, if non-empty, enables type-based filtering of
// events: the sequence includes only enclosing nodes whose type
// matches an element of the types slice.
func (c Cursor) Enclosing(types ...ast.Node) iter.Seq[Cursor] {
if c.index < 0 {
panic("Cursor.Enclosing called on Root node")
}
mask := maskOf(types)
return func(yield func(Cursor) bool) {
events := c.in.events
for i := c.index; i >= 0; i = events[i].parent {
if events[i].typ&mask != 0 && !yield(Cursor{c.in, i}) {
break
}
}
}
}
// Parent returns the parent of the current node.
//
// Parent must not be called on the Root node (whose [Cursor.Node] returns nil).
func (c Cursor) Parent() Cursor {
if c.index < 0 {
panic("Cursor.Parent called on Root node")
}
return Cursor{c.in, c.in.events[c.index].parent}
}
// ParentEdge returns the identity of the field in the parent node
// that holds this cursor's node, and if it is a list, the index within it.
//
// For example, f(x, y) is a CallExpr whose three children are Idents.
// f has edge kind [edge.CallExpr_Fun] and index -1.
// x and y have kind [edge.CallExpr_Args] and indices 0 and 1, respectively.
//
// If called on a child of the Root node, it returns ([edge.Invalid], -1).
//
// ParentEdge must not be called on the Root node (whose [Cursor.Node] returns nil).
func (c Cursor) ParentEdge() (edge.Kind, int) {
if c.index < 0 {
panic("Cursor.ParentEdge called on Root node")
}
events := c.in.events
pop := events[c.index].index
return unpackEdgeKindAndIndex(events[pop].parent)
}
// ParentEdgeKind returns the kind component of the result of [Cursor.ParentEdge].
func (c Cursor) ParentEdgeKind() edge.Kind {
ek, _ := c.ParentEdge()
return ek
}
// ParentEdgeIndex returns the index component of the result of [Cursor.ParentEdge].
func (c Cursor) ParentEdgeIndex() int {
_, index := c.ParentEdge()
return index
}
// ChildAt returns the cursor for the child of the
// current node identified by its edge and index.
// The index must be -1 if the edge.Kind is not a slice.
// The indicated child node must exist.
//
// ChildAt must not be called on the Root node (whose [Cursor.Node] returns nil).
//
// Invariant: c.Parent().ChildAt(c.ParentEdge()) == c.
func (c Cursor) ChildAt(k edge.Kind, idx int) Cursor {
target := packEdgeKindAndIndex(k, idx)
// Unfortunately there's no shortcut to looping.
events := c.in.events
i := c.index + 1
for {
pop := events[i].index
if pop < i {
break
}
if events[pop].parent == target {
return Cursor{c.in, i}
}
i = pop + 1
}
panic(fmt.Sprintf("ChildAt(%v, %d): no such child of %v", k, idx, c))
}
// Child returns the cursor for n, which must be a direct child of c's Node.
//
// Child must not be called on the Root node (whose [Cursor.Node] returns nil).
func (c Cursor) Child(n ast.Node) Cursor {
if c.index < 0 {
panic("Cursor.Child called on Root node")
}
if false {
// reference implementation
for child := range c.Children() {
if child.Node() == n {
return child
}
}
} else {
// optimized implementation
events := c.in.events
for i := c.index + 1; events[i].index > i; i = events[i].index + 1 {
if events[i].node == n {
return Cursor{c.in, i}
}
}
}
panic(fmt.Sprintf("Child(%T): not a child of %v", n, c))
}
// NextSibling returns the cursor for the next sibling node in the same list
// (for example, of files, decls, specs, statements, fields, or expressions) as
// the current node. It returns (zero, false) if the node is the last node in
// the list, or is not part of a list.
//
// NextSibling must not be called on the Root node.
//
// See note at [Cursor.Children].
func (c Cursor) NextSibling() (Cursor, bool) {
if c.index < 0 {
panic("Cursor.NextSibling called on Root node")
}
events := c.in.events
i := events[c.index].index + 1 // after corresponding pop
if i < int32(len(events)) {
if events[i].index > i { // push?
return Cursor{c.in, i}, true
}
}
return Cursor{}, false
}
// PrevSibling returns the cursor for the previous sibling node in the
// same list (for example, of files, decls, specs, statements, fields,
// or expressions) as the current node. It returns zero if the node is
// the first node in the list, or is not part of a list.
//
// It must not be called on the Root node.
//
// See note at [Cursor.Children].
func (c Cursor) PrevSibling() (Cursor, bool) {
if c.index < 0 {
panic("Cursor.PrevSibling called on Root node")
}
events := c.in.events
i := c.index - 1
if i >= 0 {
if j := events[i].index; j < i { // pop?
return Cursor{c.in, j}, true
}
}
return Cursor{}, false
}
// FirstChild returns the first direct child of the current node,
// or zero if it has no children.
func (c Cursor) FirstChild() (Cursor, bool) {
events := c.in.events
i := c.index + 1 // i=0 if c is root
if i < int32(len(events)) && events[i].index > i { // push?
return Cursor{c.in, i}, true
}
return Cursor{}, false
}
// LastChild returns the last direct child of the current node,
// or zero if it has no children.
func (c Cursor) LastChild() (Cursor, bool) {
events := c.in.events
if c.index < 0 { // root?
if len(events) > 0 {
// return push of final event (a pop)
return Cursor{c.in, events[len(events)-1].index}, true
}
} else {
j := events[c.index].index - 1 // before corresponding pop
// Inv: j == c.index if c has no children
// or j is last child's pop.
if j > c.index { // c has children
return Cursor{c.in, events[j].index}, true
}
}
return Cursor{}, false
}
// Children returns an iterator over the direct children of the
// current node, if any.
//
// When using Children, NextChild, and PrevChild, bear in mind that a
// Node's children may come from different fields, some of which may
// be lists of nodes without a distinguished intervening container
// such as [ast.BlockStmt].
//
// For example, [ast.CaseClause] has a field List of expressions and a
// field Body of statements, so the children of a CaseClause are a mix
// of expressions and statements. Other nodes that have "uncontained"
// list fields include:
//
// - [ast.ValueSpec] (Names, Values)
// - [ast.CompositeLit] (Type, Elts)
// - [ast.IndexListExpr] (X, Indices)
// - [ast.CallExpr] (Fun, Args)
// - [ast.AssignStmt] (Lhs, Rhs)
//
// So, do not assume that the previous sibling of an ast.Stmt is also
// an ast.Stmt, or if it is, that they are executed sequentially,
// unless you have established that, say, its parent is a BlockStmt
// or its [Cursor.ParentEdge] is [edge.BlockStmt_List].
// For example, given "for S1; ; S2 {}", the predecessor of S2 is S1,
// even though they are not executed in sequence.
func (c Cursor) Children() iter.Seq[Cursor] {
return func(yield func(Cursor) bool) {
c, ok := c.FirstChild()
for ok && yield(c) {
c, ok = c.NextSibling()
}
}
}
// Contains reports whether c contains or is equal to c2.
//
// Both Cursors must belong to the same [Inspector];
// neither may be its Root node.
func (c Cursor) Contains(c2 Cursor) bool {
if c.in != c2.in {
panic("different inspectors")
}
events := c.in.events
return c.index <= c2.index && events[c2.index].index <= events[c.index].index
}
// FindNode returns the cursor for node n if it belongs to the subtree
// rooted at c. It returns zero if n is not found.
func (c Cursor) FindNode(n ast.Node) (Cursor, bool) {
// FindNode is equivalent to this code,
// but more convenient and 15-20% faster:
if false {
for candidate := range c.Preorder(n) {
if candidate.Node() == n {
return candidate, true
}
}
return Cursor{}, false
}
// TODO(adonovan): opt: should we assume Node.Pos is accurate
// and combine type-based filtering with position filtering
// like FindByPos?
mask := maskOf([]ast.Node{n})
events := c.in.events
for i, limit := c.indices(); i < limit; i++ {
ev := events[i]
if ev.index > i { // push?
if ev.typ&mask != 0 && ev.node == n {
return Cursor{c.in, i}, true
}
pop := ev.index
if events[pop].typ&mask == 0 {
// Subtree does not contain type of n: skip.
i = pop
}
}
}
return Cursor{}, false
}
// FindByPos returns the cursor for the innermost node n in the tree
// rooted at c such that n.Pos() <= start && end <= n.End().
// (For an *ast.File, it uses the bounds n.FileStart-n.FileEnd.)
//
// An empty range (start == end) between two adjacent nodes is
// considered to belong to the first node.
//
// It returns zero if none is found.
// Precondition: start <= end.
//
// See also [astutil.PathEnclosingInterval], which
// tolerates adjoining whitespace.
func (c Cursor) FindByPos(start, end token.Pos) (Cursor, bool) {
if end < start {
panic("end < start")
}
events := c.in.events
// This algorithm could be implemented using c.Inspect,
// but it is about 2.5x slower.
// best is the push-index of the latest (=innermost) node containing range.
// (Beware: latest is not always innermost because FuncDecl.{Name,Type} overlap.)
best := int32(-1)
for i, limit := c.indices(); i < limit; i++ {
ev := events[i]
if ev.index > i { // push?
n := ev.node
var nodeEnd token.Pos
if file, ok := n.(*ast.File); ok {
nodeEnd = file.FileEnd
// Note: files may be out of Pos order.
if file.FileStart > start {
i = ev.index // disjoint, after; skip to next file
continue
}
} else {
// Edge case: FuncDecl.Name and .Type overlap:
// Don't update best from Name to FuncDecl.Type.
//
// The condition can be read as:
// - n is FuncType
// - n.parent is FuncDecl
// - best is strictly beneath the FuncDecl
if ev.typ == 1< ev.parent {
continue
}
nodeEnd = n.End()
if n.Pos() > start {
break // disjoint, after; stop
}
}
// Inv: node.{Pos,FileStart} <= start
if end <= nodeEnd {
// node fully contains target range
best = i
// Don't search beyond end of the first match.
// This is important only for an empty range (start=end)
// between two adjoining nodes, which would otherwise
// match both nodes; we want to match only the first.
limit = ev.index
} else if nodeEnd < start {
i = ev.index // disjoint, before; skip forward
}
}
}
if best >= 0 {
return Cursor{c.in, best}, true
}
return Cursor{}, false
}
================================================
FILE: go/ast/inspector/cursor_test.go
================================================
// Copyright 2025 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package inspector_test
import (
"fmt"
"go/ast"
"go/parser"
"go/token"
"iter"
"math/rand"
"reflect"
"slices"
"strings"
"testing"
"golang.org/x/tools/go/ast/edge"
"golang.org/x/tools/go/ast/inspector"
)
func TestCursor_Preorder(t *testing.T) {
inspect := netInspect
nodeFilter := []ast.Node{(*ast.FuncDecl)(nil), (*ast.FuncLit)(nil)}
// reference implementation
var want []ast.Node
for cur := range inspect.Root().Preorder(nodeFilter...) {
want = append(want, cur.Node())
}
// Check entire sequence.
got := slices.Collect(inspect.PreorderSeq(nodeFilter...))
compare(t, got, want)
// Check that break works.
got = got[:0]
for _, c := range firstN(10, inspect.Root().Preorder(nodeFilter...)) {
got = append(got, c.Node())
}
compare(t, got, want[:10])
}
func TestCursor_nestedTraversal(t *testing.T) {
const src = `package a
func f() {
print("hello")
}
func g() {
print("goodbye")
panic("oops")
}
`
fset := token.NewFileSet()
f, _ := parser.ParseFile(fset, "a.go", src, 0)
inspect := inspector.New([]*ast.File{f})
var (
funcDecls = []ast.Node{(*ast.FuncDecl)(nil)}
callExprs = []ast.Node{(*ast.CallExpr)(nil)}
nfuncs = 0
ncalls = 0
)
for curFunc := range inspect.Root().Preorder(funcDecls...) {
_ = curFunc.Node().(*ast.FuncDecl)
// Check edge and index.
if k, idx := curFunc.ParentEdge(); k != edge.File_Decls || idx != nfuncs {
t.Errorf("%v.ParentEdge() = (%v, %d), want edge.File_Decls, %d", curFunc, k, idx, nfuncs)
}
nfuncs++
stack := slices.Collect(curFunc.Enclosing())
// Stacks are convenient to print!
if got, want := fmt.Sprint(stack), "[*ast.FuncDecl *ast.File]"; got != want {
t.Errorf("curFunc.Enclosing() = %q, want %q", got, want)
}
// Parent, iterated, is Enclosing stack.
i := 0
for c := curFunc; c.Node() != nil; c = c.Parent() {
if got, want := stack[i], c; got != want {
t.Errorf("Enclosing[%d] = %v; Parent()^%d = %v", i, got, i, want)
}
i++
}
wantStack := "[*ast.CallExpr *ast.ExprStmt *ast.BlockStmt *ast.FuncDecl *ast.File]"
// nested Preorder traversal
preorderCount := 0
for curCall := range curFunc.Preorder(callExprs...) {
_ = curCall.Node().(*ast.CallExpr)
preorderCount++
stack := slices.Collect(curCall.Enclosing())
if got := fmt.Sprint(stack); got != wantStack {
t.Errorf("curCall.Enclosing() = %q, want %q", got, wantStack)
}
}
// nested Inspect traversal
inspectCount := 0
curFunc.Inspect(callExprs, func(curCall inspector.Cursor) (proceed bool) {
_ = curCall.Node().(*ast.CallExpr)
inspectCount++
stack := slices.Collect(curCall.Enclosing())
if got := fmt.Sprint(stack); got != wantStack {
t.Errorf("curCall.Enclosing() = %q, want %q", got, wantStack)
}
return true
})
if inspectCount != preorderCount {
t.Errorf("Inspect (%d) and Preorder (%d) events are not consistent", inspectCount, preorderCount)
}
ncalls += preorderCount
}
if nfuncs != 2 {
t.Errorf("Found %d FuncDecls, want 2", nfuncs)
}
if ncalls != 3 {
t.Errorf("Found %d CallExprs, want 3", ncalls)
}
}
func TestCursor_Children(t *testing.T) {
inspect := netInspect
// Assert that Cursor.Children agrees with
// reference implementation for every node.
var want, got []ast.Node
for c := range inspect.Root().Preorder() {
// reference implementation
want = want[:0]
{
parent := c.Node()
ast.Inspect(parent, func(n ast.Node) bool {
if n != nil && n != parent {
want = append(want, n)
}
return n == parent // descend only into parent
})
}
// Check cursor-based implementation
// (uses FirstChild+NextSibling).
got = got[:0]
for child := range c.Children() {
got = append(got, child.Node())
}
if !slices.Equal(got, want) {
t.Errorf("For %v\n"+
"Using FirstChild+NextSibling: %v\n"+
"Using ast.Inspect: %v",
c, sliceTypes(got), sliceTypes(want))
}
// Second cursor-based implementation
// using LastChild+PrevSibling+reverse.
got = got[:0]
for c, ok := c.LastChild(); ok; c, ok = c.PrevSibling() {
got = append(got, c.Node())
}
slices.Reverse(got)
if !slices.Equal(got, want) {
t.Errorf("For %v\n"+
"Using LastChild+PrevSibling: %v\n"+
"Using ast.Inspect: %v",
c, sliceTypes(got), sliceTypes(want))
}
}
}
func TestCursor_Inspect(t *testing.T) {
inspect := netInspect
// In all three loops, we'll gather both kinds of type switches,
// but we'll prune the traversal from descending into (value) switches.
switches := []ast.Node{(*ast.SwitchStmt)(nil), (*ast.TypeSwitchStmt)(nil)}
// reference implementation (ast.Inspect)
var nodesA []ast.Node
for _, f := range netFiles {
ast.Inspect(f, func(n ast.Node) (proceed bool) {
switch n.(type) {
case *ast.SwitchStmt, *ast.TypeSwitchStmt:
nodesA = append(nodesA, n)
return !is[*ast.SwitchStmt](n) // descend only into TypeSwitchStmt
}
return true
})
}
// Test Cursor.Inspect implementation.
var nodesB []ast.Node
inspect.Root().Inspect(switches, func(c inspector.Cursor) (proceed bool) {
n := c.Node()
nodesB = append(nodesB, n)
return !is[*ast.SwitchStmt](n) // descend only into TypeSwitchStmt
})
compare(t, nodesA, nodesB)
// Test WithStack implementation.
var nodesC []ast.Node
inspect.WithStack(switches, func(n ast.Node, push bool, stack []ast.Node) (proceed bool) {
if push {
nodesC = append(nodesC, n)
return !is[*ast.SwitchStmt](n) // descend only into TypeSwitchStmt
}
return false
})
compare(t, nodesA, nodesC)
}
func TestCursor_FindNode(t *testing.T) {
inspect := netInspect
// Enumerate all nodes of a particular type,
// then check that FindByPos can find them,
// starting at the root.
//
// (We use BasicLit because they are numerous.)
root := inspect.Root()
for c := range root.Preorder((*ast.BasicLit)(nil)) {
node := c.Node()
got, ok := root.FindNode(node)
if !ok {
t.Errorf("root.FindNode failed")
} else if got != c {
t.Errorf("root.FindNode returned %v, want %v", got, c)
}
}
// Same thing, but searching only within subtrees (each FuncDecl).
for funcDecl := range root.Preorder((*ast.FuncDecl)(nil)) {
for c := range funcDecl.Preorder((*ast.BasicLit)(nil)) {
node := c.Node()
got, ok := funcDecl.FindNode(node)
if !ok {
t.Errorf("funcDecl.FindNode failed")
} else if got != c {
t.Errorf("funcDecl.FindNode returned %v, want %v", got, c)
}
// Also, check that we cannot find the BasicLit
// beneath a different FuncDecl.
if prevFunc, ok := funcDecl.PrevSibling(); ok {
got, ok := prevFunc.FindNode(node)
if ok {
t.Errorf("prevFunc.FindNode succeeded unexpectedly: %v", got)
}
}
}
}
}
// TestCursor_FindByPos_order ensures that FindByPos does not assume files are in Pos order.
func TestCursor_FindByPos_order(t *testing.T) {
// Pick an arbitrary decl.
target := netFiles[7].Decls[0]
// Find the target decl by its position.
cur, ok := netInspect.Root().FindByPos(target.Pos(), target.End())
if !ok || cur.Node() != target {
t.Fatalf("unshuffled: FindByPos(%T) = (%v, %t)", target, cur, ok)
}
// Shuffle the files out of Pos order.
files := slices.Clone(netFiles)
rand.Shuffle(len(files), func(i, j int) {
files[i], files[j] = files[j], files[i]
})
// Find it again.
inspect := inspector.New(files)
cur, ok = inspect.Root().FindByPos(target.Pos(), target.End())
if !ok || cur.Node() != target {
t.Fatalf("shuffled: FindByPos(%T) = (%v, %t)", target, cur, ok)
}
}
func TestCursor_Edge(t *testing.T) {
root := netInspect.Root()
for cur := range root.Preorder() {
if cur == root {
continue // root node
}
var (
parent = cur.Parent()
e, idx = cur.ParentEdge()
)
// ast.File, child of root?
if parent.Node() == nil {
if e != edge.Invalid || idx != -1 {
t.Errorf("%v.Edge = (%v, %d), want (Invalid, -1)", cur, e, idx)
}
continue
}
// Check Edge.NodeType matches type of Parent.Node.
if e.NodeType() != reflect.TypeOf(parent.Node()) {
t.Errorf("Edge.NodeType = %v, Parent.Node has type %T",
e.NodeType(), parent.Node())
}
// Check c.Edge.Get(c.Parent.Node) == c.Node.
if got := e.Get(parent.Node(), idx); got != cur.Node() {
t.Errorf("cur=%v@%s: %s.Get(cur.Parent().Node(), %d) = %T@%s, want cur.Node()",
cur, netFset.Position(cur.Node().Pos()), e, idx, got, netFset.Position(got.Pos()))
}
// Check c.Parent.ChildAt(c.ParentEdge()) == c.
if got := parent.ChildAt(e, idx); got != cur {
t.Errorf("cur=%v@%s: cur.Parent().ChildAt(%v, %d) = %T@%s, want cur",
cur, netFset.Position(cur.Node().Pos()), e, idx, got.Node(), netFset.Position(got.Node().Pos()))
}
// Check that reflection on the parent finds the current node.
fv := reflect.ValueOf(parent.Node()).Elem().FieldByName(e.FieldName())
if idx >= 0 {
fv = fv.Index(idx) // element of []ast.Node
}
if fv.Kind() == reflect.Interface {
fv = fv.Elem() // e.g. ast.Expr -> *ast.Ident
}
got := fv.Interface().(ast.Node)
if got != cur.Node() {
t.Errorf("%v.Edge = (%v, %d); FieldName/Index reflection gave %T@%s, not original node",
cur, e, idx, got, netFset.Position(got.Pos()))
}
// Check that Cursor.Child is the reverse of Parent.
if cur.Parent().Child(cur.Node()) != cur {
t.Errorf("Cursor.Parent.Child = %v, want %v", cur.Parent().Child(cur.Node()), cur)
}
// Check invariants of Contains:
// A cursor contains itself.
if !cur.Contains(cur) {
t.Errorf("!cur.Contains(cur): %v", cur)
}
// A parent contains its child, but not the inverse.
if !parent.Contains(cur) {
t.Errorf("!cur.Parent().Contains(cur): %v", cur)
}
if cur.Contains(parent) {
t.Errorf("cur.Contains(cur.Parent()): %v", cur)
}
// A grandparent contains its grandchild, but not the inverse.
if grandparent := cur.Parent(); grandparent.Node() != nil {
if !grandparent.Contains(cur) {
t.Errorf("!cur.Parent().Parent().Contains(cur): %v", cur)
}
if cur.Contains(grandparent) {
t.Errorf("cur.Contains(cur.Parent().Parent()): %v", cur)
}
}
// A cursor and its uncle/aunt do not contain each other.
if uncle, ok := parent.NextSibling(); ok {
if uncle.Contains(cur) {
t.Errorf("cur.Parent().NextSibling().Contains(cur): %v", cur)
}
if cur.Contains(uncle) {
t.Errorf("cur.Contains(cur.Parent().NextSibling()): %v", cur)
}
}
}
}
// Regression test for mutilple matching nodes in FindByPos (#76872).
func TestCursor_FindByPos_Boundary(t *testing.T) {
// This test verifies that when a cursor position is on the boundary of two
// adjacent nodes (e.g. "foo|("), FindByPos returns the first node
// encountered in traversal order (which is usually the node "to the left").
//
// Note: The source is intentionally unformatted (no space between ')' and
// '{') to ensure the nodes are strictly adjacent at the boundary.
const src = `package p; func foo(a int){}`
var (
fset = token.NewFileSet()
f, _ = parser.ParseFile(fset, "p.go", src, 0)
tokFile = fset.File(f.FileStart)
inspect = inspector.New([]*ast.File{f})
)
d := f.Decls[0].(*ast.FuncDecl)
format := func(pos token.Pos) string {
off := tokFile.Offset(pos)
return fmt.Sprintf("...%s<<>>%s...", src[off-1:off], src[off:off+1])
}
for _, test := range []struct {
name string
pos token.Pos
want ast.Node
}{
{
// "foo|(" Ident
pos: d.Type.Params.Opening,
want: d.Name,
},
{
// ")|{" FieldList
pos: d.Body.Pos(),
want: d.Type.Params,
},
} {
cur, ok := inspect.Root().FindByPos(test.pos, test.pos)
if !ok {
t.Fatalf("FindByPos(%d) %s found nothing", test.pos, format(test.pos))
}
if cur.Node() != test.want {
t.Errorf("FindByPos(%d) %s:\ngot %T (%v)\nwant %T (%v)",
test.pos, format(test.pos),
cur.Node(), cur.Node(),
test.want, test.want)
}
}
}
// Regression test for FuncDecl.Type irregularity in FindByPos (#75997).
func TestCursor_FindByPos(t *testing.T) {
// Observe that the range of FuncType has a hole between
// the "func" token and the start of Type.Params.
// The hole contains FuncDecl.{Recv,Name}.
//
// ~~~~~~~~~~~~FuncDecl~~~~~~~~~~~~~~~~~~~~~~~~~
// ~Recv~ ~Name~
// ~~~~--------------~~~~FuncType~~~~~~
// ~Params~ ~Results~
const src = `package a; func (recv) method(params) (results) { body }`
var (
fset = token.NewFileSet()
f, _ = parser.ParseFile(fset, "a.go", src, 0) // ignore parse errors
tokFile = fset.File(f.FileStart)
inspect = inspector.New([]*ast.File{f})
)
format := func(start, end token.Pos) string {
var (
startOffset = tokFile.Offset(start)
endOffset = tokFile.Offset(end)
)
return fmt.Sprintf("%s<<%s>>%s", src[:startOffset], src[startOffset:endOffset], src[endOffset:])
}
d := f.Decls[0].(*ast.FuncDecl)
// Each test case specifies a [pos-end) range for
// FindByPos and the syntax node it should find.
for _, test := range []struct {
start, end token.Pos
want ast.Node
}{
// pure subtrees
{d.Pos(), d.End(), d}, // decl
{d.Recv.Pos(), d.Recv.End(), d.Recv}, // recv
{d.Name.Pos(), d.Name.End(), d.Name}, // name
// (A FuncDecl can't have both Recv and TypeParams, so skip this one.)
// {d.Type.TypeParams.Pos(), d.Type.TypeParams.End(), d.Type.TypeParams},
{d.Type.Params.Pos(), d.Type.Params.End(), d.Type.Params}, // params
{d.Type.Results.Pos(), d.Type.Results.End(), d.Type.Results}, // results
{d.Body.Pos(), d.Body.End(), d.Body}, // body
// single tokens
{
// "func"
d.Type.Func, d.Type.Func + 4,
d, // arguably this should be d.Type
},
{
// "(" FieldList
d.Recv.Pos(), d.Recv.Pos() + 1,
d.Recv,
},
{
// "recv" Ident
d.Recv.List[0].Pos(), d.Recv.List[0].Pos() + 1,
d.Recv.List[0].Type,
},
{
// "name" Ident
d.Name.Pos(), d.Name.Pos() + 1,
d.Name,
},
{
// "(" FieldList
d.Type.Params.Pos(), d.Type.Params.Pos() + 1,
d.Type.Params,
},
{
// "params" Ident
d.Type.Params.List[0].Pos(), d.Type.Params.List[0].Pos() + 1,
d.Type.Params.List[0].Type,
},
{
// "(" FieldList
d.Type.Results.Pos(), d.Type.Results.Pos() + 1,
d.Type.Results,
},
{
// "results" Ident
d.Type.Results.List[0].Pos(), d.Type.Results.List[0].Pos() + 1,
d.Type.Results.List[0].Type,
},
{
// "{" BlockStmt
d.Body.Pos(), d.Body.Pos() + 1,
d.Body,
},
{
// "body" Ident
d.Body.List[0].Pos(), d.Body.List[0].Pos() + 1,
d.Body.List[0].(*ast.ExprStmt).X,
},
} {
cur, ok := inspect.Root().FindByPos(test.start, test.end)
if !ok || cur.Node() == nil {
t.Errorf("%s: FindByPos failed", format(test.start, test.end))
continue
}
got := cur.Node()
if got != test.want {
t.Errorf("FindByPos:\ninput:\t%s\ngot:\t%s (%T)\nwant:\t%s (%T)",
format(test.start, test.end),
format(got.Pos(), got.End()), got,
format(test.want.Pos(), test.want.End()), test.want)
}
}
}
func is[T any](x any) bool {
_, ok := x.(T)
return ok
}
// sliceTypes is a debugging helper that formats each slice element with %T.
func sliceTypes[T any](slice []T) string {
var buf strings.Builder
buf.WriteByte('[')
for i, elem := range slice {
if i > 0 {
buf.WriteByte(' ')
}
fmt.Fprintf(&buf, "%T", elem)
}
buf.WriteByte(']')
return buf.String()
}
func BenchmarkInspectCalls(b *testing.B) {
inspect := netInspect
// Measure marginal cost of traversal.
callExprs := []ast.Node{(*ast.CallExpr)(nil)}
b.Run("Preorder", func(b *testing.B) {
var ncalls int
for b.Loop() {
inspect.Preorder(callExprs, func(n ast.Node) {
_ = n.(*ast.CallExpr)
ncalls++
})
}
})
b.Run("WithStack", func(b *testing.B) {
var ncalls int
for b.Loop() {
inspect.WithStack(callExprs, func(n ast.Node, push bool, stack []ast.Node) (proceed bool) {
_ = n.(*ast.CallExpr)
if push {
ncalls++
}
return true
})
}
})
b.Run("Cursor", func(b *testing.B) {
var ncalls int
for b.Loop() {
for cur := range inspect.Root().Preorder(callExprs...) {
_ = cur.Node().(*ast.CallExpr)
ncalls++
}
}
})
b.Run("CursorEnclosing", func(b *testing.B) {
var ncalls int
for b.Loop() {
for cur := range inspect.Root().Preorder(callExprs...) {
_ = cur.Node().(*ast.CallExpr)
for range cur.Enclosing() {
}
ncalls++
}
}
})
}
// This benchmark compares methods for finding a known node in a tree.
func BenchmarkCursor_FindNode(b *testing.B) {
root := netInspect.Root()
callExprs := []ast.Node{(*ast.CallExpr)(nil)}
// Choose a needle in the haystack to use as the search target:
// a CallExpr not too near the start nor at too shallow a depth.
var needle inspector.Cursor
{
count := 0
found := false
for c := range root.Preorder(callExprs...) {
count++
if count >= 1000 && iterlen(c.Enclosing()) >= 6 {
needle = c
found = true
break
}
}
if !found {
b.Fatal("can't choose needle")
}
}
b.ResetTimer()
b.Run("Cursor.Preorder", func(b *testing.B) {
needleNode := needle.Node()
for b.Loop() {
var found inspector.Cursor
for c := range root.Preorder(callExprs...) {
if c.Node() == needleNode {
found = c
break
}
}
if found != needle {
b.Errorf("Preorder search failed: got %v, want %v", found, needle)
}
}
})
// This method is about 10-15% faster than Cursor.Preorder.
b.Run("Cursor.FindNode", func(b *testing.B) {
for b.Loop() {
found, ok := root.FindNode(needle.Node())
if !ok || found != needle {
b.Errorf("FindNode search failed: got %v, want %v", found, needle)
}
}
})
// This method is about 100x (!) faster than Cursor.Preorder.
b.Run("Cursor.FindByPos", func(b *testing.B) {
needleNode := needle.Node()
for b.Loop() {
found, ok := root.FindByPos(needleNode.Pos(), needleNode.End())
if !ok || found != needle {
b.Errorf("FindByPos search failed: got %v, want %v", found, needle)
}
}
})
}
func iterlen[T any](seq iter.Seq[T]) (len int) {
for range seq {
len++
}
return
}
================================================
FILE: go/ast/inspector/inspector.go
================================================
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package inspector provides helper functions for traversal over the
// syntax trees of a package, including node filtering by type, and
// materialization of the traversal stack.
//
// During construction, the inspector does a complete traversal and
// builds a list of push/pop events and their node type. Subsequent
// method calls that request a traversal scan this list, rather than walk
// the AST, and perform type filtering using efficient bit sets.
// This representation is sometimes called a "balanced parenthesis tree."
//
// Experiments suggest the inspector's traversals are about 2.5x faster
// than [ast.Inspect], but it may take around 5 traversals for this
// benefit to amortize the inspector's construction cost.
// If efficiency is the primary concern, do not use Inspector for
// one-off traversals.
//
// The [Cursor] type provides a more flexible API for efficient
// navigation of syntax trees in all four "cardinal directions". For
// example, traversals may be nested, so you can find each node of
// type A and then search within it for nodes of type B. Or you can
// traverse from a node to its immediate neighbors: its parent, its
// previous and next sibling, or its first and last child. We
// recommend using methods of Cursor in preference to Inspector where
// possible.
package inspector
// There are four orthogonal features in a traversal:
// 1 type filtering
// 2 pruning
// 3 postorder calls to f
// 4 stack
// Rather than offer all of them in the API,
// only a few combinations are exposed:
// - Preorder is the fastest and has fewest features,
// but is the most commonly needed traversal.
// - Nodes and WithStack both provide pruning and postorder calls,
// even though few clients need it, because supporting two versions
// is not justified.
// More combinations could be supported by expressing them as
// wrappers around a more generic traversal, but this was measured
// and found to degrade performance significantly (30%).
import (
"go/ast"
"golang.org/x/tools/go/ast/edge"
)
// An Inspector provides methods for inspecting
// (traversing) the syntax trees of a package.
type Inspector struct {
events []event
}
func packEdgeKindAndIndex(ek edge.Kind, index int) int32 {
return int32(uint32(index+1)<<7 | uint32(ek))
}
// unpackEdgeKindAndIndex unpacks the edge kind and edge index (within
// an []ast.Node slice) from the parent field of a pop event.
func unpackEdgeKindAndIndex(x int32) (edge.Kind, int) {
// The "parent" field of a pop node holds the
// edge Kind in the lower 7 bits and the index+1
// in the upper 25.
return edge.Kind(x & 0x7f), int(x>>7) - 1
}
// New returns an Inspector for the specified syntax trees.
func New(files []*ast.File) *Inspector {
return &Inspector{traverse(files)}
}
// An event represents a push or a pop
// of an ast.Node during a traversal.
type event struct {
node ast.Node
typ uint64 // typeOf(node) on push event, or union of typ strictly between push and pop events on pop events
index int32 // index of corresponding push or pop event
parent int32 // index of parent's push node (push nodes only), or packed edge kind/index (pop nodes only)
}
// TODO: Experiment with storing only the second word of event.node (unsafe.Pointer).
// Type can be recovered from the sole bit in typ.
// [Tried this, wasn't faster. --adonovan]
// Preorder visits all the nodes of the files supplied to [New] in
// depth-first order. It calls f(n) for each node n before it visits
// n's children.
//
// The complete traversal sequence is determined by [ast.Inspect].
// The types argument, if non-empty, enables type-based filtering of
// events. The function f is called only for nodes whose type
// matches an element of the types slice.
//
// The [Cursor.Preorder] method provides a richer alternative interface.
// Example:
//
// for c := range in.Root().Preorder(types) { ... }
func (in *Inspector) Preorder(types []ast.Node, f func(ast.Node)) {
// Because it avoids postorder calls to f, and the pruning
// check, Preorder is almost twice as fast as Nodes. The two
// features seem to contribute similar slowdowns (~1.4x each).
// This function is equivalent to the PreorderSeq call below,
// but to avoid the additional dynamic call (which adds 13-35%
// to the benchmarks), we expand it out.
//
// in.PreorderSeq(types...)(func(n ast.Node) bool {
// f(n)
// return true
// })
mask := maskOf(types)
for i := int32(0); i < int32(len(in.events)); {
ev := in.events[i]
if ev.index > i {
// push
if ev.typ&mask != 0 {
f(ev.node)
}
pop := ev.index
if in.events[pop].typ&mask == 0 {
// Subtrees do not contain types: skip them and pop.
i = pop + 1
continue
}
}
i++
}
}
// Nodes visits the nodes of the files supplied to [New] in depth-first
// order. It calls f(n, true) for each node n before it visits n's
// children. If f returns true, Nodes invokes f recursively for each
// of the non-nil children of the node, followed by a call of
// f(n, false).
//
// The complete traversal sequence is determined by [ast.Inspect].
// The types argument, if non-empty, enables type-based filtering of
// events. The function f if is called only for nodes whose type
// matches an element of the types slice.
//
// The [Cursor.Inspect] method provides a richer alternative interface.
// Example:
//
// in.Root().Inspect(types, func(c Cursor) bool {
// ...
// return true
// }
func (in *Inspector) Nodes(types []ast.Node, f func(n ast.Node, push bool) (proceed bool)) {
mask := maskOf(types)
for i := int32(0); i < int32(len(in.events)); {
ev := in.events[i]
if ev.index > i {
// push
pop := ev.index
if ev.typ&mask != 0 {
if !f(ev.node, true) {
i = pop + 1 // jump to corresponding pop + 1
continue
}
}
if in.events[pop].typ&mask == 0 {
// Subtrees do not contain types: skip them.
i = pop
continue
}
} else {
// pop
push := ev.index
if in.events[push].typ&mask != 0 {
f(ev.node, false)
}
}
i++
}
}
// WithStack visits nodes in a similar manner to Nodes, but it
// supplies each call to f an additional argument, the current
// traversal stack. The stack's first element is the outermost node,
// an *ast.File; its last is the innermost, n.
//
// The [Cursor.Inspect] method provides a richer alternative interface.
// Example:
//
// in.Root().Inspect(types, func(c Cursor) bool {
// stack := slices.Collect(c.Enclosing())
// ...
// return true
// })
func (in *Inspector) WithStack(types []ast.Node, f func(n ast.Node, push bool, stack []ast.Node) (proceed bool)) {
mask := maskOf(types)
var stack []ast.Node
for i := int32(0); i < int32(len(in.events)); {
ev := in.events[i]
if ev.index > i {
// push
pop := ev.index
stack = append(stack, ev.node)
if ev.typ&mask != 0 {
if !f(ev.node, true, stack) {
i = pop + 1
stack = stack[:len(stack)-1]
continue
}
}
if in.events[pop].typ&mask == 0 {
// Subtrees does not contain types: skip them.
i = pop
continue
}
} else {
// pop
push := ev.index
if in.events[push].typ&mask != 0 {
f(ev.node, false, stack)
}
stack = stack[:len(stack)-1]
}
i++
}
}
// traverse builds the table of events representing a traversal.
func traverse(files []*ast.File) []event {
// Preallocate approximate number of events
// based on source file extent of the declarations.
// (We use End-Pos not FileStart-FileEnd to neglect
// the effect of long doc comments.)
// This makes traverse faster by 4x (!).
var extent int
for _, f := range files {
extent += int(f.End() - f.Pos())
}
// This estimate is based on the net/http package.
capacity := min(extent*33/100, 1e6) // impose some reasonable maximum (1M)
v := &visitor{
events: make([]event, 0, capacity),
stack: []item{{index: -1}}, // include an extra event so file nodes have a parent
}
for _, file := range files {
walk(v, edge.Invalid, -1, file)
}
return v.events
}
type visitor struct {
events []event
stack []item
}
type item struct {
index int32 // index of current node's push event
parentIndex int32 // index of parent node's push event
typAccum uint64 // accumulated type bits of current node's descendants
edgeKindAndIndex int32 // edge.Kind and index, bit packed
}
func (v *visitor) push(ek edge.Kind, eindex int, node ast.Node) {
var (
index = int32(len(v.events))
parentIndex = v.stack[len(v.stack)-1].index
)
v.events = append(v.events, event{
node: node,
parent: parentIndex,
typ: typeOf(node),
index: 0, // (pop index is set later by visitor.pop)
})
v.stack = append(v.stack, item{
index: index,
parentIndex: parentIndex,
edgeKindAndIndex: packEdgeKindAndIndex(ek, eindex),
})
// 2B nodes ought to be enough for anyone!
if int32(len(v.events)) < 0 {
panic("event index exceeded int32")
}
// 32M elements in an []ast.Node ought to be enough for anyone!
if ek2, eindex2 := unpackEdgeKindAndIndex(packEdgeKindAndIndex(ek, eindex)); ek2 != ek || eindex2 != eindex {
panic("Node slice index exceeded uint25")
}
}
func (v *visitor) pop(node ast.Node) {
top := len(v.stack) - 1
current := v.stack[top]
push := &v.events[current.index]
parent := &v.stack[top-1]
push.index = int32(len(v.events)) // make push event refer to pop
parent.typAccum |= current.typAccum | push.typ // accumulate type bits into parent
v.stack = v.stack[:top]
v.events = append(v.events, event{
node: node,
typ: current.typAccum,
index: current.index,
parent: current.edgeKindAndIndex, // see [unpackEdgeKindAndIndex]
})
}
================================================
FILE: go/ast/inspector/inspector_test.go
================================================
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package inspector_test
import (
"go/ast"
"go/build"
"go/parser"
"go/token"
"log"
"path/filepath"
"reflect"
"strconv"
"strings"
"testing"
"golang.org/x/tools/go/ast/inspector"
)
// net/http package
var (
netFset = token.NewFileSet()
netFiles []*ast.File
netInspect *inspector.Inspector
)
func init() {
files, err := parseNetFiles()
if err != nil {
log.Fatal(err)
}
netFiles = files
netInspect = inspector.New(netFiles)
}
func parseNetFiles() ([]*ast.File, error) {
pkg, err := build.Default.Import("net", "", 0)
if err != nil {
return nil, err
}
var files []*ast.File
for _, filename := range pkg.GoFiles {
filename = filepath.Join(pkg.Dir, filename)
f, err := parser.ParseFile(netFset, filename, nil, 0)
if err != nil {
return nil, err
}
files = append(files, f)
}
return files, nil
}
// TestInspectAllNodes compares Inspector against ast.Inspect.
func TestInspectAllNodes(t *testing.T) {
inspect := inspector.New(netFiles)
var nodesA []ast.Node
inspect.Nodes(nil, func(n ast.Node, push bool) bool {
if push {
nodesA = append(nodesA, n)
}
return true
})
var nodesB []ast.Node
for _, f := range netFiles {
ast.Inspect(f, func(n ast.Node) bool {
if n != nil {
nodesB = append(nodesB, n)
}
return true
})
}
compare(t, nodesA, nodesB)
}
func TestInspectGenericNodes(t *testing.T) {
// src is using the 16 identifiers i0, i1, ... i15 so
// we can easily verify that we've found all of them.
const src = `package a
type I interface { ~i0|i1 }
type T[i2, i3 interface{ ~i4 }] struct {}
func f[i5, i6 any]() {
_ = f[i7, i8]
var x T[i9, i10]
}
func (*T[i11, i12]) m()
var _ i13[i14, i15]
`
fset := token.NewFileSet()
f, _ := parser.ParseFile(fset, "a.go", src, 0)
inspect := inspector.New([]*ast.File{f})
found := make([]bool, 16)
indexListExprs := make(map[*ast.IndexListExpr]bool)
// Verify that we reach all i* identifiers, and collect IndexListExpr nodes.
inspect.Preorder(nil, func(n ast.Node) {
switch n := n.(type) {
case *ast.Ident:
if n.Name[0] == 'i' {
index, err := strconv.Atoi(n.Name[1:])
if err != nil {
t.Fatal(err)
}
found[index] = true
}
case *ast.IndexListExpr:
indexListExprs[n] = false
}
})
for i, v := range found {
if !v {
t.Errorf("missed identifier i%d", i)
}
}
// Verify that we can filter to IndexListExprs that we found in the first
// step.
if len(indexListExprs) == 0 {
t.Fatal("no index list exprs found")
}
inspect.Preorder([]ast.Node{&ast.IndexListExpr{}}, func(n ast.Node) {
ix := n.(*ast.IndexListExpr)
indexListExprs[ix] = true
})
for ix, v := range indexListExprs {
if !v {
t.Errorf("inspected node %v not filtered", ix)
}
}
}
// TestInspectPruning compares Inspector against ast.Inspect,
// pruning descent within ast.CallExpr nodes.
func TestInspectPruning(t *testing.T) {
inspect := inspector.New(netFiles)
var nodesA []ast.Node
inspect.Nodes(nil, func(n ast.Node, push bool) bool {
if push {
nodesA = append(nodesA, n)
_, isCall := n.(*ast.CallExpr)
return !isCall // don't descend into function calls
}
return false
})
var nodesB []ast.Node
for _, f := range netFiles {
ast.Inspect(f, func(n ast.Node) bool {
if n != nil {
nodesB = append(nodesB, n)
_, isCall := n.(*ast.CallExpr)
return !isCall // don't descend into function calls
}
return false
})
}
compare(t, nodesA, nodesB)
}
// compare calls t.Error if !slices.Equal(nodesA, nodesB).
func compare[N comparable](t *testing.T, nodesA, nodesB []N) {
if len(nodesA) != len(nodesB) {
t.Errorf("inconsistent node lists: %d vs %d", len(nodesA), len(nodesB))
} else {
for i := range nodesA {
if a, b := nodesA[i], nodesB[i]; a != b {
t.Errorf("node %d is inconsistent: %T, %T", i, a, b)
}
}
}
}
func TestTypeFiltering(t *testing.T) {
const src = `package a
func f() {
print("hi")
panic("oops")
}
`
fset := token.NewFileSet()
f, _ := parser.ParseFile(fset, "a.go", src, 0)
inspect := inspector.New([]*ast.File{f})
var got []string
fn := func(n ast.Node, push bool) bool {
if push {
got = append(got, typeOf(n))
}
return true
}
// no type filtering
inspect.Nodes(nil, fn)
if want := strings.Fields("File Ident FuncDecl Ident FuncType FieldList BlockStmt ExprStmt CallExpr Ident BasicLit ExprStmt CallExpr Ident BasicLit"); !reflect.DeepEqual(got, want) {
t.Errorf("inspect: got %s, want %s", got, want)
}
// type filtering
nodeTypes := []ast.Node{
(*ast.BasicLit)(nil),
(*ast.CallExpr)(nil),
}
got = nil
inspect.Nodes(nodeTypes, fn)
if want := strings.Fields("CallExpr BasicLit CallExpr BasicLit"); !reflect.DeepEqual(got, want) {
t.Errorf("inspect: got %s, want %s", got, want)
}
// inspect with stack
got = nil
inspect.WithStack(nodeTypes, func(n ast.Node, push bool, stack []ast.Node) bool {
if push {
var line []string
for _, n := range stack {
line = append(line, typeOf(n))
}
got = append(got, strings.Join(line, " "))
}
return true
})
want := []string{
"File FuncDecl BlockStmt ExprStmt CallExpr",
"File FuncDecl BlockStmt ExprStmt CallExpr BasicLit",
"File FuncDecl BlockStmt ExprStmt CallExpr",
"File FuncDecl BlockStmt ExprStmt CallExpr BasicLit",
}
if !reflect.DeepEqual(got, want) {
t.Errorf("inspect: got %s, want %s", got, want)
}
}
func typeOf(n ast.Node) string {
return strings.TrimPrefix(reflect.TypeOf(n).String(), "*ast.")
}
// The numbers show a marginal improvement (ASTInspect/Inspect) of 3.5x,
// but a break-even point (NewInspector/(ASTInspect-Inspect)) of about 5
// traversals.
//
// BenchmarkASTInspect 1.0 ms
// BenchmarkNewInspector 2.2 ms
// BenchmarkInspect 0.39ms
// BenchmarkInspectFilter 0.01ms
// BenchmarkInspectCalls 0.14ms
func BenchmarkNewInspector(b *testing.B) {
// Measure one-time construction overhead.
for b.Loop() {
inspector.New(netFiles)
}
}
func BenchmarkInspect(b *testing.B) {
inspect := inspector.New(netFiles)
// Measure marginal cost of traversal.
var ndecls, nlits int
for b.Loop() {
inspect.Preorder(nil, func(n ast.Node) {
switch n.(type) {
case *ast.FuncDecl:
ndecls++
case *ast.FuncLit:
nlits++
}
})
}
}
func BenchmarkInspectFilter(b *testing.B) {
inspect := inspector.New(netFiles)
// Measure marginal cost of traversal.
nodeFilter := []ast.Node{(*ast.FuncDecl)(nil), (*ast.FuncLit)(nil)}
var ndecls, nlits int
for b.Loop() {
inspect.Preorder(nodeFilter, func(n ast.Node) {
switch n.(type) {
case *ast.FuncDecl:
ndecls++
case *ast.FuncLit:
nlits++
}
})
}
}
func BenchmarkASTInspect(b *testing.B) {
var ndecls, nlits int
for b.Loop() {
for _, f := range netFiles {
ast.Inspect(f, func(n ast.Node) bool {
switch n.(type) {
case *ast.FuncDecl:
ndecls++
case *ast.FuncLit:
nlits++
}
return true
})
}
}
}
================================================
FILE: go/ast/inspector/iter.go
================================================
// Copyright 2024 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build go1.23
package inspector
import (
"go/ast"
"iter"
)
// PreorderSeq returns an iterator that visits all the
// nodes of the files supplied to [New] in depth-first order.
// It visits each node n before n's children.
// The complete traversal sequence is determined by ast.Inspect.
//
// The types argument, if non-empty, enables type-based filtering:
// only nodes whose type matches an element of the types slice are
// included in the sequence.
//
// Example:
//
// for call := range in.PreorderSeq((*ast.CallExpr)(nil)) { ... }
//
// The [All] function is more convenient if there is exactly one node type:
//
// for call := range All[*ast.CallExpr](in) { ... }
//
// See also the newer and more flexible [Cursor] API, which lets you
// start the traversal at an arbitrary node, and reports each matching
// node by its Cursor, enabling easier navigation.
// The above example would be written thus:
//
// for curCall := range in.Root().Preorder((*ast.CallExpr)(nil)) {
// call := curCall.Node().(*ast.CallExpr)
// ...
// }
func (in *Inspector) PreorderSeq(types ...ast.Node) iter.Seq[ast.Node] {
// This implementation is identical to Preorder,
// except that it supports breaking out of the loop.
return func(yield func(ast.Node) bool) {
mask := maskOf(types)
for i := int32(0); i < int32(len(in.events)); {
ev := in.events[i]
if ev.index > i {
// push
if ev.typ&mask != 0 {
if !yield(ev.node) {
break
}
}
pop := ev.index
if in.events[pop].typ&mask == 0 {
// Subtrees do not contain types: skip them and pop.
i = pop + 1
continue
}
}
i++
}
}
}
// All[N] returns an iterator over all the nodes of type N.
// N must be a pointer-to-struct type that implements ast.Node.
//
// Example:
//
// for call := range All[*ast.CallExpr](in) { ... }
//
// See also the newer and more flexible [Cursor] API, which lets you
// start the traversal at an arbitrary node, and reports each matching
// node by its Cursor, enabling easier navigation.
// The above example would be written thus:
//
// for curCall := range in.Root().Preorder((*ast.CallExpr)(nil)) {
// call := curCall.Node().(*ast.CallExpr)
// ...
// }
func All[N interface {
*S
ast.Node
}, S any](in *Inspector) iter.Seq[N] {
// To avoid additional dynamic call overheads,
// we duplicate rather than call the logic of PreorderSeq.
mask := typeOf((N)(nil))
return func(yield func(N) bool) {
for i := int32(0); i < int32(len(in.events)); {
ev := in.events[i]
if ev.index > i {
// push
if ev.typ&mask != 0 {
if !yield(ev.node.(N)) {
break
}
}
pop := ev.index
if in.events[pop].typ&mask == 0 {
// Subtrees do not contain types: skip them and pop.
i = pop + 1
continue
}
}
i++
}
}
}
================================================
FILE: go/ast/inspector/iter_test.go
================================================
// Copyright 2024 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package inspector_test
import (
"go/ast"
"iter"
"slices"
"testing"
"golang.org/x/tools/go/ast/inspector"
)
// TestPreorderSeq checks PreorderSeq against Preorder.
func TestPreorderSeq(t *testing.T) {
inspect := inspector.New(netFiles)
nodeFilter := []ast.Node{(*ast.FuncDecl)(nil), (*ast.FuncLit)(nil)}
// reference implementation
var want []ast.Node
inspect.Preorder(nodeFilter, func(n ast.Node) {
want = append(want, n)
})
// Check entire sequence.
got := slices.Collect(inspect.PreorderSeq(nodeFilter...))
compare(t, got, want)
// Check that break works.
got = firstN(10, inspect.PreorderSeq(nodeFilter...))
compare(t, got, want[:10])
}
// TestAll checks All against Preorder.
func TestAll(t *testing.T) {
inspect := inspector.New(netFiles)
// reference implementation
var want []*ast.CallExpr
inspect.Preorder([]ast.Node{(*ast.CallExpr)(nil)}, func(n ast.Node) {
want = append(want, n.(*ast.CallExpr))
})
// Check entire sequence.
got := slices.Collect(inspector.All[*ast.CallExpr](inspect))
compare(t, got, want)
// Check that break works.
got = firstN(10, inspector.All[*ast.CallExpr](inspect))
compare(t, got, want[:10])
}
// firstN(n, seq), returns a slice of up to n elements of seq.
func firstN[T any](n int, seq iter.Seq[T]) (res []T) {
for x := range seq {
res = append(res, x)
if len(res) == n {
break
}
}
return res
}
// BenchmarkAllCalls is like BenchmarkInspectCalls,
// but using the single-type filtering iterator, All.
// (The iterator adds about 5-15%.)
func BenchmarkAllCalls(b *testing.B) {
inspect := inspector.New(netFiles)
// Measure marginal cost of traversal.
var ncalls int
for b.Loop() {
for range inspector.All[*ast.CallExpr](inspect) {
ncalls++
}
}
}
================================================
FILE: go/ast/inspector/typeof.go
================================================
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package inspector
// This file defines func typeOf(ast.Node) uint64.
//
// The initial map-based implementation was too slow;
// see https://go-review.googlesource.com/c/tools/+/135655/1/go/ast/inspector/inspector.go#196
import (
"go/ast"
"math"
)
const (
nArrayType = iota
nAssignStmt
nBadDecl
nBadExpr
nBadStmt
nBasicLit
nBinaryExpr
nBlockStmt
nBranchStmt
nCallExpr
nCaseClause
nChanType
nCommClause
nComment
nCommentGroup
nCompositeLit
nDeclStmt
nDeferStmt
nEllipsis
nEmptyStmt
nExprStmt
nField
nFieldList
nFile
nForStmt
nFuncDecl
nFuncLit
nFuncType
nGenDecl
nGoStmt
nIdent
nIfStmt
nImportSpec
nIncDecStmt
nIndexExpr
nIndexListExpr
nInterfaceType
nKeyValueExpr
nLabeledStmt
nMapType
nPackage
nParenExpr
nRangeStmt
nReturnStmt
nSelectStmt
nSelectorExpr
nSendStmt
nSliceExpr
nStarExpr
nStructType
nSwitchStmt
nTypeAssertExpr
nTypeSpec
nTypeSwitchStmt
nUnaryExpr
nValueSpec
)
// typeOf returns a distinct single-bit value that represents the type of n.
//
// Various implementations were benchmarked with BenchmarkNewInspector:
//
// GOGC=off
// - type switch 4.9-5.5ms 2.1ms
// - binary search over a sorted list of types 5.5-5.9ms 2.5ms
// - linear scan, frequency-ordered list 5.9-6.1ms 2.7ms
// - linear scan, unordered list 6.4ms 2.7ms
// - hash table 6.5ms 3.1ms
//
// A perfect hash seemed like overkill.
//
// The compiler's switch statement is the clear winner
// as it produces a binary tree in code,
// with constant conditions and good branch prediction.
// (Sadly it is the most verbose in source code.)
// Binary search suffered from poor branch prediction.
func typeOf(n ast.Node) uint64 {
// Fast path: nearly half of all nodes are identifiers.
if _, ok := n.(*ast.Ident); ok {
return 1 << nIdent
}
// These cases include all nodes encountered by ast.Inspect.
switch n.(type) {
case *ast.ArrayType:
return 1 << nArrayType
case *ast.AssignStmt:
return 1 << nAssignStmt
case *ast.BadDecl:
return 1 << nBadDecl
case *ast.BadExpr:
return 1 << nBadExpr
case *ast.BadStmt:
return 1 << nBadStmt
case *ast.BasicLit:
return 1 << nBasicLit
case *ast.BinaryExpr:
return 1 << nBinaryExpr
case *ast.BlockStmt:
return 1 << nBlockStmt
case *ast.BranchStmt:
return 1 << nBranchStmt
case *ast.CallExpr:
return 1 << nCallExpr
case *ast.CaseClause:
return 1 << nCaseClause
case *ast.ChanType:
return 1 << nChanType
case *ast.CommClause:
return 1 << nCommClause
case *ast.Comment:
return 1 << nComment
case *ast.CommentGroup:
return 1 << nCommentGroup
case *ast.CompositeLit:
return 1 << nCompositeLit
case *ast.DeclStmt:
return 1 << nDeclStmt
case *ast.DeferStmt:
return 1 << nDeferStmt
case *ast.Ellipsis:
return 1 << nEllipsis
case *ast.EmptyStmt:
return 1 << nEmptyStmt
case *ast.ExprStmt:
return 1 << nExprStmt
case *ast.Field:
return 1 << nField
case *ast.FieldList:
return 1 << nFieldList
case *ast.File:
return 1 << nFile
case *ast.ForStmt:
return 1 << nForStmt
case *ast.FuncDecl:
return 1 << nFuncDecl
case *ast.FuncLit:
return 1 << nFuncLit
case *ast.FuncType:
return 1 << nFuncType
case *ast.GenDecl:
return 1 << nGenDecl
case *ast.GoStmt:
return 1 << nGoStmt
case *ast.Ident:
return 1 << nIdent
case *ast.IfStmt:
return 1 << nIfStmt
case *ast.ImportSpec:
return 1 << nImportSpec
case *ast.IncDecStmt:
return 1 << nIncDecStmt
case *ast.IndexExpr:
return 1 << nIndexExpr
case *ast.IndexListExpr:
return 1 << nIndexListExpr
case *ast.InterfaceType:
return 1 << nInterfaceType
case *ast.KeyValueExpr:
return 1 << nKeyValueExpr
case *ast.LabeledStmt:
return 1 << nLabeledStmt
case *ast.MapType:
return 1 << nMapType
case *ast.Package:
return 1 << nPackage
case *ast.ParenExpr:
return 1 << nParenExpr
case *ast.RangeStmt:
return 1 << nRangeStmt
case *ast.ReturnStmt:
return 1 << nReturnStmt
case *ast.SelectStmt:
return 1 << nSelectStmt
case *ast.SelectorExpr:
return 1 << nSelectorExpr
case *ast.SendStmt:
return 1 << nSendStmt
case *ast.SliceExpr:
return 1 << nSliceExpr
case *ast.StarExpr:
return 1 << nStarExpr
case *ast.StructType:
return 1 << nStructType
case *ast.SwitchStmt:
return 1 << nSwitchStmt
case *ast.TypeAssertExpr:
return 1 << nTypeAssertExpr
case *ast.TypeSpec:
return 1 << nTypeSpec
case *ast.TypeSwitchStmt:
return 1 << nTypeSwitchStmt
case *ast.UnaryExpr:
return 1 << nUnaryExpr
case *ast.ValueSpec:
return 1 << nValueSpec
}
return 0
}
func maskOf(nodes []ast.Node) uint64 {
if len(nodes) == 0 {
return math.MaxUint64 // match all node types
}
var mask uint64
for _, n := range nodes {
mask |= typeOf(n)
}
return mask
}
================================================
FILE: go/ast/inspector/walk.go
================================================
// Copyright 2025 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package inspector
// This file is a fork of ast.Inspect to reduce unnecessary dynamic
// calls and to gather edge information.
//
// Consistency with the original is ensured by TestInspectAllNodes.
import (
"fmt"
"go/ast"
"golang.org/x/tools/go/ast/edge"
)
func walkList[N ast.Node](v *visitor, ek edge.Kind, list []N) {
for i, node := range list {
walk(v, ek, i, node)
}
}
func walk(v *visitor, ek edge.Kind, index int, node ast.Node) {
v.push(ek, index, node)
// walk children
// (the order of the cases matches the order
// of the corresponding node types in ast.go)
switch n := node.(type) {
// Comments and fields
case *ast.Comment:
// nothing to do
case *ast.CommentGroup:
walkList(v, edge.CommentGroup_List, n.List)
case *ast.Field:
if n.Doc != nil {
walk(v, edge.Field_Doc, -1, n.Doc)
}
walkList(v, edge.Field_Names, n.Names)
if n.Type != nil {
walk(v, edge.Field_Type, -1, n.Type)
}
if n.Tag != nil {
walk(v, edge.Field_Tag, -1, n.Tag)
}
if n.Comment != nil {
walk(v, edge.Field_Comment, -1, n.Comment)
}
case *ast.FieldList:
walkList(v, edge.FieldList_List, n.List)
// Expressions
case *ast.BadExpr, *ast.Ident, *ast.BasicLit:
// nothing to do
case *ast.Ellipsis:
if n.Elt != nil {
walk(v, edge.Ellipsis_Elt, -1, n.Elt)
}
case *ast.FuncLit:
walk(v, edge.FuncLit_Type, -1, n.Type)
walk(v, edge.FuncLit_Body, -1, n.Body)
case *ast.CompositeLit:
if n.Type != nil {
walk(v, edge.CompositeLit_Type, -1, n.Type)
}
walkList(v, edge.CompositeLit_Elts, n.Elts)
case *ast.ParenExpr:
walk(v, edge.ParenExpr_X, -1, n.X)
case *ast.SelectorExpr:
walk(v, edge.SelectorExpr_X, -1, n.X)
walk(v, edge.SelectorExpr_Sel, -1, n.Sel)
case *ast.IndexExpr:
walk(v, edge.IndexExpr_X, -1, n.X)
walk(v, edge.IndexExpr_Index, -1, n.Index)
case *ast.IndexListExpr:
walk(v, edge.IndexListExpr_X, -1, n.X)
walkList(v, edge.IndexListExpr_Indices, n.Indices)
case *ast.SliceExpr:
walk(v, edge.SliceExpr_X, -1, n.X)
if n.Low != nil {
walk(v, edge.SliceExpr_Low, -1, n.Low)
}
if n.High != nil {
walk(v, edge.SliceExpr_High, -1, n.High)
}
if n.Max != nil {
walk(v, edge.SliceExpr_Max, -1, n.Max)
}
case *ast.TypeAssertExpr:
walk(v, edge.TypeAssertExpr_X, -1, n.X)
if n.Type != nil {
walk(v, edge.TypeAssertExpr_Type, -1, n.Type)
}
case *ast.CallExpr:
walk(v, edge.CallExpr_Fun, -1, n.Fun)
walkList(v, edge.CallExpr_Args, n.Args)
case *ast.StarExpr:
walk(v, edge.StarExpr_X, -1, n.X)
case *ast.UnaryExpr:
walk(v, edge.UnaryExpr_X, -1, n.X)
case *ast.BinaryExpr:
walk(v, edge.BinaryExpr_X, -1, n.X)
walk(v, edge.BinaryExpr_Y, -1, n.Y)
case *ast.KeyValueExpr:
walk(v, edge.KeyValueExpr_Key, -1, n.Key)
walk(v, edge.KeyValueExpr_Value, -1, n.Value)
// Types
case *ast.ArrayType:
if n.Len != nil {
walk(v, edge.ArrayType_Len, -1, n.Len)
}
walk(v, edge.ArrayType_Elt, -1, n.Elt)
case *ast.StructType:
walk(v, edge.StructType_Fields, -1, n.Fields)
case *ast.FuncType:
if n.TypeParams != nil {
walk(v, edge.FuncType_TypeParams, -1, n.TypeParams)
}
if n.Params != nil {
walk(v, edge.FuncType_Params, -1, n.Params)
}
if n.Results != nil {
walk(v, edge.FuncType_Results, -1, n.Results)
}
case *ast.InterfaceType:
walk(v, edge.InterfaceType_Methods, -1, n.Methods)
case *ast.MapType:
walk(v, edge.MapType_Key, -1, n.Key)
walk(v, edge.MapType_Value, -1, n.Value)
case *ast.ChanType:
walk(v, edge.ChanType_Value, -1, n.Value)
// Statements
case *ast.BadStmt:
// nothing to do
case *ast.DeclStmt:
walk(v, edge.DeclStmt_Decl, -1, n.Decl)
case *ast.EmptyStmt:
// nothing to do
case *ast.LabeledStmt:
walk(v, edge.LabeledStmt_Label, -1, n.Label)
walk(v, edge.LabeledStmt_Stmt, -1, n.Stmt)
case *ast.ExprStmt:
walk(v, edge.ExprStmt_X, -1, n.X)
case *ast.SendStmt:
walk(v, edge.SendStmt_Chan, -1, n.Chan)
walk(v, edge.SendStmt_Value, -1, n.Value)
case *ast.IncDecStmt:
walk(v, edge.IncDecStmt_X, -1, n.X)
case *ast.AssignStmt:
walkList(v, edge.AssignStmt_Lhs, n.Lhs)
walkList(v, edge.AssignStmt_Rhs, n.Rhs)
case *ast.GoStmt:
walk(v, edge.GoStmt_Call, -1, n.Call)
case *ast.DeferStmt:
walk(v, edge.DeferStmt_Call, -1, n.Call)
case *ast.ReturnStmt:
walkList(v, edge.ReturnStmt_Results, n.Results)
case *ast.BranchStmt:
if n.Label != nil {
walk(v, edge.BranchStmt_Label, -1, n.Label)
}
case *ast.BlockStmt:
walkList(v, edge.BlockStmt_List, n.List)
case *ast.IfStmt:
if n.Init != nil {
walk(v, edge.IfStmt_Init, -1, n.Init)
}
walk(v, edge.IfStmt_Cond, -1, n.Cond)
walk(v, edge.IfStmt_Body, -1, n.Body)
if n.Else != nil {
walk(v, edge.IfStmt_Else, -1, n.Else)
}
case *ast.CaseClause:
walkList(v, edge.CaseClause_List, n.List)
walkList(v, edge.CaseClause_Body, n.Body)
case *ast.SwitchStmt:
if n.Init != nil {
walk(v, edge.SwitchStmt_Init, -1, n.Init)
}
if n.Tag != nil {
walk(v, edge.SwitchStmt_Tag, -1, n.Tag)
}
walk(v, edge.SwitchStmt_Body, -1, n.Body)
case *ast.TypeSwitchStmt:
if n.Init != nil {
walk(v, edge.TypeSwitchStmt_Init, -1, n.Init)
}
walk(v, edge.TypeSwitchStmt_Assign, -1, n.Assign)
walk(v, edge.TypeSwitchStmt_Body, -1, n.Body)
case *ast.CommClause:
if n.Comm != nil {
walk(v, edge.CommClause_Comm, -1, n.Comm)
}
walkList(v, edge.CommClause_Body, n.Body)
case *ast.SelectStmt:
walk(v, edge.SelectStmt_Body, -1, n.Body)
case *ast.ForStmt:
if n.Init != nil {
walk(v, edge.ForStmt_Init, -1, n.Init)
}
if n.Cond != nil {
walk(v, edge.ForStmt_Cond, -1, n.Cond)
}
if n.Post != nil {
walk(v, edge.ForStmt_Post, -1, n.Post)
}
walk(v, edge.ForStmt_Body, -1, n.Body)
case *ast.RangeStmt:
if n.Key != nil {
walk(v, edge.RangeStmt_Key, -1, n.Key)
}
if n.Value != nil {
walk(v, edge.RangeStmt_Value, -1, n.Value)
}
walk(v, edge.RangeStmt_X, -1, n.X)
walk(v, edge.RangeStmt_Body, -1, n.Body)
// Declarations
case *ast.ImportSpec:
if n.Doc != nil {
walk(v, edge.ImportSpec_Doc, -1, n.Doc)
}
if n.Name != nil {
walk(v, edge.ImportSpec_Name, -1, n.Name)
}
walk(v, edge.ImportSpec_Path, -1, n.Path)
if n.Comment != nil {
walk(v, edge.ImportSpec_Comment, -1, n.Comment)
}
case *ast.ValueSpec:
if n.Doc != nil {
walk(v, edge.ValueSpec_Doc, -1, n.Doc)
}
walkList(v, edge.ValueSpec_Names, n.Names)
if n.Type != nil {
walk(v, edge.ValueSpec_Type, -1, n.Type)
}
walkList(v, edge.ValueSpec_Values, n.Values)
if n.Comment != nil {
walk(v, edge.ValueSpec_Comment, -1, n.Comment)
}
case *ast.TypeSpec:
if n.Doc != nil {
walk(v, edge.TypeSpec_Doc, -1, n.Doc)
}
walk(v, edge.TypeSpec_Name, -1, n.Name)
if n.TypeParams != nil {
walk(v, edge.TypeSpec_TypeParams, -1, n.TypeParams)
}
walk(v, edge.TypeSpec_Type, -1, n.Type)
if n.Comment != nil {
walk(v, edge.TypeSpec_Comment, -1, n.Comment)
}
case *ast.BadDecl:
// nothing to do
case *ast.GenDecl:
if n.Doc != nil {
walk(v, edge.GenDecl_Doc, -1, n.Doc)
}
walkList(v, edge.GenDecl_Specs, n.Specs)
case *ast.FuncDecl:
if n.Doc != nil {
walk(v, edge.FuncDecl_Doc, -1, n.Doc)
}
if n.Recv != nil {
walk(v, edge.FuncDecl_Recv, -1, n.Recv)
}
walk(v, edge.FuncDecl_Name, -1, n.Name)
walk(v, edge.FuncDecl_Type, -1, n.Type)
if n.Body != nil {
walk(v, edge.FuncDecl_Body, -1, n.Body)
}
case *ast.File:
if n.Doc != nil {
walk(v, edge.File_Doc, -1, n.Doc)
}
walk(v, edge.File_Name, -1, n.Name)
walkList(v, edge.File_Decls, n.Decls)
// don't walk n.Comments - they have been
// visited already through the individual
// nodes
default:
// (includes *ast.Package)
panic(fmt.Sprintf("Walk: unexpected node type %T", n))
}
v.pop(node)
}
================================================
FILE: go/buildutil/allpackages.go
================================================
// Copyright 2014 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package buildutil provides utilities related to the go/build
// package in the standard library.
//
// All I/O is done via the build.Context file system interface, which must
// be concurrency-safe.
package buildutil // import "golang.org/x/tools/go/buildutil"
import (
"go/build"
"os"
"path/filepath"
"sort"
"strings"
"sync"
)
// AllPackages returns the package path of each Go package in any source
// directory of the specified build context (e.g. $GOROOT or an element
// of $GOPATH). Errors are ignored. The results are sorted.
// All package paths are canonical, and thus may contain "/vendor/".
//
// The result may include import paths for directories that contain no
// *.go files, such as "archive" (in $GOROOT/src).
//
// All I/O is done via the build.Context file system interface,
// which must be concurrency-safe.
func AllPackages(ctxt *build.Context) []string {
var list []string
ForEachPackage(ctxt, func(pkg string, _ error) {
list = append(list, pkg)
})
sort.Strings(list)
return list
}
// ForEachPackage calls the found function with the package path of
// each Go package it finds in any source directory of the specified
// build context (e.g. $GOROOT or an element of $GOPATH).
// All package paths are canonical, and thus may contain "/vendor/".
//
// If the package directory exists but could not be read, the second
// argument to the found function provides the error.
//
// All I/O is done via the build.Context file system interface,
// which must be concurrency-safe.
func ForEachPackage(ctxt *build.Context, found func(importPath string, err error)) {
ch := make(chan item)
var wg sync.WaitGroup
for _, root := range ctxt.SrcDirs() {
wg.Add(1)
go func() {
allPackages(ctxt, root, ch)
wg.Done()
}()
}
go func() {
wg.Wait()
close(ch)
}()
// All calls to found occur in the caller's goroutine.
for i := range ch {
found(i.importPath, i.err)
}
}
type item struct {
importPath string
err error // (optional)
}
// We use a process-wide counting semaphore to limit
// the number of parallel calls to ReadDir.
var ioLimit = make(chan bool, 20)
func allPackages(ctxt *build.Context, root string, ch chan<- item) {
root = filepath.Clean(root) + string(os.PathSeparator)
var wg sync.WaitGroup
var walkDir func(dir string)
walkDir = func(dir string) {
// Avoid .foo, _foo, and testdata directory trees.
base := filepath.Base(dir)
if base == "" || base[0] == '.' || base[0] == '_' || base == "testdata" {
return
}
pkg := filepath.ToSlash(strings.TrimPrefix(dir, root))
// Prune search if we encounter any of these import paths.
switch pkg {
case "builtin":
return
}
ioLimit <- true
files, err := ReadDir(ctxt, dir)
<-ioLimit
if pkg != "" || err != nil {
ch <- item{pkg, err}
}
for _, fi := range files {
if fi.IsDir() {
wg.Add(1)
go func() {
walkDir(filepath.Join(dir, fi.Name()))
wg.Done()
}()
}
}
}
walkDir(root)
wg.Wait()
}
// ExpandPatterns returns the set of packages matched by patterns,
// which may have the following forms:
//
// golang.org/x/tools/cmd/guru # a single package
// golang.org/x/tools/... # all packages beneath dir
// ... # the entire workspace.
//
// Order is significant: a pattern preceded by '-' removes matching
// packages from the set. For example, these patterns match all encoding
// packages except encoding/xml:
//
// encoding/... -encoding/xml
//
// A trailing slash in a pattern is ignored. (Path components of Go
// package names are separated by slash, not the platform's path separator.)
func ExpandPatterns(ctxt *build.Context, patterns []string) map[string]bool {
// TODO(adonovan): support other features of 'go list':
// - "std"/"cmd"/"all" meta-packages
// - "..." not at the end of a pattern
// - relative patterns using "./" or "../" prefix
pkgs := make(map[string]bool)
doPkg := func(pkg string, neg bool) {
if neg {
delete(pkgs, pkg)
} else {
pkgs[pkg] = true
}
}
// Scan entire workspace if wildcards are present.
// TODO(adonovan): opt: scan only the necessary subtrees of the workspace.
var all []string
for _, arg := range patterns {
if strings.HasSuffix(arg, "...") {
all = AllPackages(ctxt)
break
}
}
for _, arg := range patterns {
if arg == "" {
continue
}
neg := arg[0] == '-'
if neg {
arg = arg[1:]
}
if arg == "..." {
// ... matches all packages
for _, pkg := range all {
doPkg(pkg, neg)
}
} else if dir, ok := strings.CutSuffix(arg, "/..."); ok {
// dir/... matches all packages beneath dir
for _, pkg := range all {
if strings.HasPrefix(pkg, dir) &&
(len(pkg) == len(dir) || pkg[len(dir)] == '/') {
doPkg(pkg, neg)
}
}
} else {
// single package
doPkg(strings.TrimSuffix(arg, "/"), neg)
}
}
return pkgs
}
================================================
FILE: go/buildutil/allpackages_test.go
================================================
// Copyright 2014 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Incomplete source tree on Android.
//go:build !android
package buildutil_test
import (
"go/build"
"runtime"
"sort"
"strings"
"testing"
"golang.org/x/tools/go/buildutil"
"golang.org/x/tools/internal/packagestest"
)
func TestAllPackages(t *testing.T) {
if runtime.Compiler == "gccgo" {
t.Skip("gccgo has no standard packages")
}
exported := packagestest.Export(t, packagestest.GOPATH, []packagestest.Module{
{Name: "golang.org/x/tools/go/buildutil", Files: packagestest.MustCopyFileTree(".")}})
defer exported.Cleanup()
var gopath string
for _, env := range exported.Config.Env {
if !strings.HasPrefix(env, "GOPATH=") {
continue
}
gopath = strings.TrimPrefix(env, "GOPATH=")
}
if gopath == "" {
t.Fatal("Failed to fish GOPATH out of env: ", exported.Config.Env)
}
var buildContext = build.Default
buildContext.GOPATH = gopath
all := buildutil.AllPackages(&buildContext)
set := make(map[string]bool)
for _, pkg := range all {
set[pkg] = true
}
const wantAtLeast = 250
if len(all) < wantAtLeast {
t.Errorf("Found only %d packages, want at least %d", len(all), wantAtLeast)
}
for _, want := range []string{"fmt", "crypto/sha256", "golang.org/x/tools/go/buildutil"} {
if !set[want] {
t.Errorf("Package %q not found; got %s", want, all)
}
}
}
func TestExpandPatterns(t *testing.T) {
tree := make(map[string]map[string]string)
for _, pkg := range []string{
"encoding",
"encoding/xml",
"encoding/hex",
"encoding/json",
"fmt",
} {
tree[pkg] = make(map[string]string)
}
ctxt := buildutil.FakeContext(tree)
for _, test := range []struct {
patterns string
want string
}{
{"", ""},
{"fmt", "fmt"},
{"nosuchpkg", "nosuchpkg"},
{"nosuchdir/...", ""},
{"...", "encoding encoding/hex encoding/json encoding/xml fmt"},
{"encoding/... -encoding/xml", "encoding encoding/hex encoding/json"},
{"... -encoding/...", "fmt"},
{"encoding", "encoding"},
{"encoding/", "encoding"},
} {
var pkgs []string
for pkg := range buildutil.ExpandPatterns(ctxt, strings.Fields(test.patterns)) {
pkgs = append(pkgs, pkg)
}
sort.Strings(pkgs)
got := strings.Join(pkgs, " ")
if got != test.want {
t.Errorf("ExpandPatterns(%s) = %s, want %s",
test.patterns, got, test.want)
}
}
}
================================================
FILE: go/buildutil/fakecontext.go
================================================
// Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package buildutil
import (
"fmt"
"go/build"
"io"
"os"
"path"
"path/filepath"
"sort"
"strings"
"time"
)
// FakeContext returns a build.Context for the fake file tree specified
// by pkgs, which maps package import paths to a mapping from file base
// names to contents.
//
// The fake Context has a GOROOT of "/go" and no GOPATH, and overrides
// the necessary file access methods to read from memory instead of the
// real file system.
//
// Unlike a real file tree, the fake one has only two levels---packages
// and files---so ReadDir("/go/src/") returns all packages under
// /go/src/ including, for instance, "math" and "math/big".
// ReadDir("/go/src/math/big") would return all the files in the
// "math/big" package.
func FakeContext(pkgs map[string]map[string]string) *build.Context {
clean := func(filename string) string {
f := path.Clean(filepath.ToSlash(filename))
// Removing "/go/src" while respecting segment
// boundaries has this unfortunate corner case:
if f == "/go/src" {
return ""
}
return strings.TrimPrefix(f, "/go/src/")
}
ctxt := build.Default // copy
ctxt.GOROOT = "/go"
ctxt.GOPATH = ""
ctxt.Compiler = "gc"
ctxt.IsDir = func(dir string) bool {
dir = clean(dir)
if dir == "" {
return true // needed by (*build.Context).SrcDirs
}
return pkgs[dir] != nil
}
ctxt.ReadDir = func(dir string) ([]os.FileInfo, error) {
dir = clean(dir)
var fis []os.FileInfo
if dir == "" {
// enumerate packages
for importPath := range pkgs {
fis = append(fis, fakeDirInfo(importPath))
}
} else {
// enumerate files of package
for basename := range pkgs[dir] {
fis = append(fis, fakeFileInfo(basename))
}
}
sort.Sort(byName(fis))
return fis, nil
}
ctxt.OpenFile = func(filename string) (io.ReadCloser, error) {
filename = clean(filename)
dir, base := path.Split(filename)
content, ok := pkgs[path.Clean(dir)][base]
if !ok {
return nil, fmt.Errorf("file not found: %s", filename)
}
return io.NopCloser(strings.NewReader(content)), nil
}
ctxt.IsAbsPath = func(path string) bool {
path = filepath.ToSlash(path)
// Don't rely on the default (filepath.Path) since on
// Windows, it reports virtual paths as non-absolute.
return strings.HasPrefix(path, "/")
}
return &ctxt
}
type byName []os.FileInfo
func (s byName) Len() int { return len(s) }
func (s byName) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
func (s byName) Less(i, j int) bool { return s[i].Name() < s[j].Name() }
type fakeFileInfo string
func (fi fakeFileInfo) Name() string { return string(fi) }
func (fakeFileInfo) Sys() any { return nil }
func (fakeFileInfo) ModTime() time.Time { return time.Time{} }
func (fakeFileInfo) IsDir() bool { return false }
func (fakeFileInfo) Size() int64 { return 0 }
func (fakeFileInfo) Mode() os.FileMode { return 0644 }
type fakeDirInfo string
func (fd fakeDirInfo) Name() string { return string(fd) }
func (fakeDirInfo) Sys() any { return nil }
func (fakeDirInfo) ModTime() time.Time { return time.Time{} }
func (fakeDirInfo) IsDir() bool { return true }
func (fakeDirInfo) Size() int64 { return 0 }
func (fakeDirInfo) Mode() os.FileMode { return 0755 }
================================================
FILE: go/buildutil/overlay.go
================================================
// Copyright 2016 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package buildutil
import (
"bufio"
"bytes"
"fmt"
"go/build"
"io"
"path/filepath"
"strconv"
"strings"
)
// OverlayContext overlays a build.Context with additional files from
// a map. Files in the map take precedence over other files.
//
// In addition to plain string comparison, two file names are
// considered equal if their base names match and their directory
// components point at the same directory on the file system. That is,
// symbolic links are followed for directories, but not files.
//
// A common use case for OverlayContext is to allow editors to pass in
// a set of unsaved, modified files.
//
// Currently, only the Context.OpenFile function will respect the
// overlay. This may change in the future.
func OverlayContext(orig *build.Context, overlay map[string][]byte) *build.Context {
// TODO(dominikh): Implement IsDir, HasSubdir and ReadDir
rc := func(data []byte) (io.ReadCloser, error) {
return io.NopCloser(bytes.NewBuffer(data)), nil
}
copy := *orig // make a copy
ctxt := ©
ctxt.OpenFile = func(path string) (io.ReadCloser, error) {
// Fast path: names match exactly.
if content, ok := overlay[path]; ok {
return rc(content)
}
// Slow path: check for same file under a different
// alias, perhaps due to a symbolic link.
for filename, content := range overlay {
if sameFile(path, filename) {
return rc(content)
}
}
return OpenFile(orig, path)
}
return ctxt
}
// ParseOverlayArchive parses an archive containing Go files and their
// contents. The result is intended to be used with OverlayContext.
//
// # Archive format
//
// The archive consists of a series of files. Each file consists of a
// name, a decimal file size and the file contents, separated by
// newlines. No newline follows after the file contents.
func ParseOverlayArchive(archive io.Reader) (map[string][]byte, error) {
overlay := make(map[string][]byte)
r := bufio.NewReader(archive)
for {
// Read file name.
filename, err := r.ReadString('\n')
if err != nil {
if err == io.EOF {
break // OK
}
return nil, fmt.Errorf("reading archive file name: %v", err)
}
filename = filepath.Clean(strings.TrimSpace(filename))
// Read file size.
sz, err := r.ReadString('\n')
if err != nil {
return nil, fmt.Errorf("reading size of archive file %s: %v", filename, err)
}
sz = strings.TrimSpace(sz)
size, err := strconv.ParseUint(sz, 10, 32)
if err != nil {
return nil, fmt.Errorf("parsing size of archive file %s: %v", filename, err)
}
// Read file content.
content := make([]byte, size)
if _, err := io.ReadFull(r, content); err != nil {
return nil, fmt.Errorf("reading archive file %s: %v", filename, err)
}
overlay[filename] = content
}
return overlay, nil
}
================================================
FILE: go/buildutil/overlay_test.go
================================================
// Copyright 2016 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package buildutil_test
import (
"go/build"
"io"
"reflect"
"strings"
"testing"
"golang.org/x/tools/go/buildutil"
)
func TestParseOverlayArchive(t *testing.T) {
var tt = []struct {
in string
out map[string][]byte
hasErr bool
}{
{
"a.go\n5\n12345",
map[string][]byte{"a.go": []byte("12345")},
false,
},
{
"a.go\n5\n1234",
nil,
true,
},
{
"a.go\n5\n12345b.go\n4\n1234",
map[string][]byte{"a.go": []byte("12345"), "b.go": []byte("1234")},
false,
},
}
for _, test := range tt {
got, err := buildutil.ParseOverlayArchive(strings.NewReader(test.in))
if err == nil && test.hasErr {
t.Errorf("expected error for %q", test.in)
}
if err != nil && !test.hasErr {
t.Errorf("unexpected error %v for %q", err, test.in)
}
if !reflect.DeepEqual(got, test.out) {
t.Errorf("got %#v, want %#v", got, test.out)
}
}
}
func TestOverlay(t *testing.T) {
ctx := &build.Default
ov := map[string][]byte{
"/somewhere/a.go": []byte("file contents"),
}
names := []string{"/somewhere/a.go", "/somewhere//a.go"}
ctx = buildutil.OverlayContext(ctx, ov)
for _, name := range names {
f, err := buildutil.OpenFile(ctx, name)
if err != nil {
t.Errorf("unexpected error %v", err)
}
b, err := io.ReadAll(f)
if err != nil {
t.Errorf("unexpected error %v", err)
}
if got, expected := string(b), string(ov["/somewhere/a.go"]); got != expected {
t.Errorf("read %q, expected %q", got, expected)
}
}
}
================================================
FILE: go/buildutil/tags.go
================================================
// Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package buildutil
// This duplicated logic must be kept in sync with that from go build:
// $GOROOT/src/cmd/go/internal/work/build.go (tagsFlag.Set)
// $GOROOT/src/cmd/go/internal/base/flag.go (StringsFlag.Set)
// $GOROOT/src/cmd/internal/quoted/quoted.go (isSpaceByte, Split)
import (
"fmt"
"strings"
)
const TagsFlagDoc = "a list of `build tags` to consider satisfied during the build. " +
"For more information about build tags, see the description of " +
"build constraints in the documentation for the go/build package"
// TagsFlag is an implementation of the flag.Value and flag.Getter interfaces that parses
// a flag value the same as go build's -tags flag and populates a []string slice.
//
// See $GOROOT/src/go/build/doc.go for description of build tags.
// See $GOROOT/src/cmd/go/doc.go for description of 'go build -tags' flag.
//
// Example:
//
// flag.Var((*buildutil.TagsFlag)(&build.Default.BuildTags), "tags", buildutil.TagsFlagDoc)
type TagsFlag []string
func (v *TagsFlag) Set(s string) error {
// See $GOROOT/src/cmd/go/internal/work/build.go (tagsFlag.Set)
// For compatibility with Go 1.12 and earlier, allow "-tags='a b c'" or even just "-tags='a'".
if strings.Contains(s, " ") || strings.Contains(s, "'") {
var err error
*v, err = splitQuotedFields(s)
if *v == nil {
*v = []string{}
}
return err
}
// Starting in Go 1.13, the -tags flag is a comma-separated list of build tags.
*v = []string{}
for s := range strings.SplitSeq(s, ",") {
if s != "" {
*v = append(*v, s)
}
}
return nil
}
func (v *TagsFlag) Get() any { return *v }
func splitQuotedFields(s string) ([]string, error) {
// See $GOROOT/src/cmd/internal/quoted/quoted.go (Split)
// This must remain in sync with that logic.
var f []string
for len(s) > 0 {
for len(s) > 0 && isSpaceByte(s[0]) {
s = s[1:]
}
if len(s) == 0 {
break
}
// Accepted quoted string. No unescaping inside.
if s[0] == '"' || s[0] == '\'' {
quote := s[0]
s = s[1:]
i := 0
for i < len(s) && s[i] != quote {
i++
}
if i >= len(s) {
return nil, fmt.Errorf("unterminated %c string", quote)
}
f = append(f, s[:i])
s = s[i+1:]
continue
}
i := 0
for i < len(s) && !isSpaceByte(s[i]) {
i++
}
f = append(f, s[:i])
s = s[i:]
}
return f, nil
}
func (v *TagsFlag) String() string {
return ""
}
func isSpaceByte(c byte) bool {
// See $GOROOT/src/cmd/internal/quoted/quoted.go (isSpaceByte, Split)
// This list must remain in sync with that.
return c == ' ' || c == '\t' || c == '\n' || c == '\r'
}
================================================
FILE: go/buildutil/tags_test.go
================================================
// Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package buildutil_test
import (
"bytes"
"flag"
"go/build"
"os/exec"
"reflect"
"strings"
"testing"
"golang.org/x/tools/go/buildutil"
"golang.org/x/tools/internal/testenv"
)
func TestTags(t *testing.T) {
type tagTestCase struct {
tags string
want []string
wantErr bool
}
for name, tc := range map[string]tagTestCase{
// Normal valid cases
"empty": {
tags: "",
want: []string{},
},
"commas": {
tags: "tag1,tag_2,🐹,tag/3,tag-4",
want: []string{"tag1", "tag_2", "🐹", "tag/3", "tag-4"},
},
"delimiters are spaces": {
tags: "a b\tc\rd\ne",
want: []string{"a", "b", "c", "d", "e"},
},
"old quote and space form": {
tags: "'a' 'b' 'c'",
want: []string{"a", "b", "c"},
},
// Normal error cases
"unterminated": {
tags: `"missing closing quote`,
want: []string{},
wantErr: true,
},
"unterminated single": {
tags: `'missing closing quote`,
want: []string{},
wantErr: true,
},
// Maybe surprising difference for unterminated quotes, no spaces
"unterminated no spaces": {
tags: `"missing_closing_quote`,
want: []string{"\"missing_closing_quote"},
},
"unterminated no spaces single": {
tags: `'missing_closing_quote`,
want: []string{},
wantErr: true,
},
// Permitted but not recommended
"delimiters contiguous spaces": {
tags: "a \t\r\n, b \t\r\nc,d\te\tf",
want: []string{"a", ",", "b", "c,d", "e", "f"},
},
"quotes and spaces": {
tags: ` 'one'"two" 'three "four"'`,
want: []string{"one", "two", "three \"four\""},
},
"quotes single no spaces": {
tags: `'t1','t2',"t3"`,
want: []string{"t1", ",'t2',\"t3\""},
},
"quotes double no spaces": {
tags: `"t1","t2","t3"`,
want: []string{`"t1"`, `"t2"`, `"t3"`},
},
} {
t.Run(name, func(t *testing.T) {
f := flag.NewFlagSet("TestTags", flag.ContinueOnError)
var ctxt build.Context
f.Var((*buildutil.TagsFlag)(&ctxt.BuildTags), "tags", buildutil.TagsFlagDoc)
// Normal case valid parsed tags
f.Parse([]string{"-tags", tc.tags, "rest"})
// BuildTags
if !reflect.DeepEqual(ctxt.BuildTags, tc.want) {
t.Errorf("Case = %s, BuildTags = %q, want %q", name, ctxt.BuildTags, tc.want)
}
// Args()
if want := []string{"rest"}; !reflect.DeepEqual(f.Args(), want) {
t.Errorf("Case = %s, f.Args() = %q, want %q", name, f.Args(), want)
}
// Regression check against base go tooling
cmd := testenv.Command(t, "go", "list", "-f", "{{context.BuildTags}}", "-tags", tc.tags, ".")
var out bytes.Buffer
cmd.Stdout = &out
if err := cmd.Run(); err != nil {
if ee, ok := err.(*exec.ExitError); ok && len(ee.Stderr) > 0 {
t.Logf("stderr:\n%s", ee.Stderr)
}
if !tc.wantErr {
t.Errorf("%v: %v", cmd, err)
}
} else if tc.wantErr {
t.Errorf("Expected failure for %v", cmd)
} else {
wantDescription := strings.Join(tc.want, " ")
output := strings.Trim(strings.TrimSuffix(out.String(), "\n"), "[]")
if output != wantDescription {
t.Errorf("Output = %s, want %s", output, wantDescription)
}
}
})
}
}
================================================
FILE: go/buildutil/util.go
================================================
// Copyright 2014 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package buildutil
import (
"fmt"
"go/ast"
"go/build"
"go/parser"
"go/token"
"io"
"io/ioutil"
"os"
"path"
"path/filepath"
"strings"
)
// ParseFile behaves like parser.ParseFile,
// but uses the build context's file system interface, if any.
//
// If file is not absolute (as defined by IsAbsPath), the (dir, file)
// components are joined using JoinPath; dir must be absolute.
//
// The displayPath function, if provided, is used to transform the
// filename that will be attached to the ASTs.
//
// TODO(adonovan): call this from go/loader.parseFiles when the tree thaws.
func ParseFile(fset *token.FileSet, ctxt *build.Context, displayPath func(string) string, dir string, file string, mode parser.Mode) (*ast.File, error) {
if !IsAbsPath(ctxt, file) {
file = JoinPath(ctxt, dir, file)
}
rd, err := OpenFile(ctxt, file)
if err != nil {
return nil, err
}
defer rd.Close() // ignore error
if displayPath != nil {
file = displayPath(file)
}
return parser.ParseFile(fset, file, rd, mode)
}
// ContainingPackage returns the package containing filename.
//
// If filename is not absolute, it is interpreted relative to working directory dir.
// All I/O is via the build context's file system interface, if any.
//
// The '...Files []string' fields of the resulting build.Package are not
// populated (build.FindOnly mode).
func ContainingPackage(ctxt *build.Context, dir, filename string) (*build.Package, error) {
if !IsAbsPath(ctxt, filename) {
filename = JoinPath(ctxt, dir, filename)
}
// We must not assume the file tree uses
// "/" always,
// `\` always,
// or os.PathSeparator (which varies by platform),
// but to make any progress, we are forced to assume that
// paths will not use `\` unless the PathSeparator
// is also `\`, thus we can rely on filepath.ToSlash for some sanity.
dirSlash := path.Dir(filepath.ToSlash(filename)) + "/"
// We assume that no source root (GOPATH[i] or GOROOT) contains any other.
for _, srcdir := range ctxt.SrcDirs() {
srcdirSlash := filepath.ToSlash(srcdir) + "/"
if importPath, ok := HasSubdir(ctxt, srcdirSlash, dirSlash); ok {
return ctxt.Import(importPath, dir, build.FindOnly)
}
}
return nil, fmt.Errorf("can't find package containing %s", filename)
}
// -- Effective methods of file system interface -------------------------
// (go/build.Context defines these as methods, but does not export them.)
// HasSubdir calls ctxt.HasSubdir (if not nil) or else uses
// the local file system to answer the question.
func HasSubdir(ctxt *build.Context, root, dir string) (rel string, ok bool) {
if f := ctxt.HasSubdir; f != nil {
return f(root, dir)
}
// Try using paths we received.
if rel, ok = hasSubdir(root, dir); ok {
return
}
// Try expanding symlinks and comparing
// expanded against unexpanded and
// expanded against expanded.
rootSym, _ := filepath.EvalSymlinks(root)
dirSym, _ := filepath.EvalSymlinks(dir)
if rel, ok = hasSubdir(rootSym, dir); ok {
return
}
if rel, ok = hasSubdir(root, dirSym); ok {
return
}
return hasSubdir(rootSym, dirSym)
}
func hasSubdir(root, dir string) (rel string, ok bool) {
const sep = string(filepath.Separator)
root = filepath.Clean(root)
if !strings.HasSuffix(root, sep) {
root += sep
}
dir = filepath.Clean(dir)
if !strings.HasPrefix(dir, root) {
return "", false
}
return filepath.ToSlash(dir[len(root):]), true
}
// FileExists returns true if the specified file exists,
// using the build context's file system interface.
func FileExists(ctxt *build.Context, path string) bool {
if ctxt.OpenFile != nil {
r, err := ctxt.OpenFile(path)
if err != nil {
return false
}
r.Close() // ignore error
return true
}
_, err := os.Stat(path)
return err == nil
}
// OpenFile behaves like os.Open,
// but uses the build context's file system interface, if any.
func OpenFile(ctxt *build.Context, path string) (io.ReadCloser, error) {
if ctxt.OpenFile != nil {
return ctxt.OpenFile(path)
}
return os.Open(path)
}
// IsAbsPath behaves like filepath.IsAbs,
// but uses the build context's file system interface, if any.
func IsAbsPath(ctxt *build.Context, path string) bool {
if ctxt.IsAbsPath != nil {
return ctxt.IsAbsPath(path)
}
return filepath.IsAbs(path)
}
// JoinPath behaves like filepath.Join,
// but uses the build context's file system interface, if any.
func JoinPath(ctxt *build.Context, path ...string) string {
if ctxt.JoinPath != nil {
return ctxt.JoinPath(path...)
}
return filepath.Join(path...)
}
// IsDir behaves like os.Stat plus IsDir,
// but uses the build context's file system interface, if any.
func IsDir(ctxt *build.Context, path string) bool {
if ctxt.IsDir != nil {
return ctxt.IsDir(path)
}
fi, err := os.Stat(path)
return err == nil && fi.IsDir()
}
// ReadDir behaves like ioutil.ReadDir,
// but uses the build context's file system interface, if any.
func ReadDir(ctxt *build.Context, path string) ([]os.FileInfo, error) {
if ctxt.ReadDir != nil {
return ctxt.ReadDir(path)
}
return ioutil.ReadDir(path)
}
// SplitPathList behaves like filepath.SplitList,
// but uses the build context's file system interface, if any.
func SplitPathList(ctxt *build.Context, s string) []string {
if ctxt.SplitPathList != nil {
return ctxt.SplitPathList(s)
}
return filepath.SplitList(s)
}
// sameFile returns true if x and y have the same basename and denote
// the same file.
func sameFile(x, y string) bool {
if path.Clean(x) == path.Clean(y) {
return true
}
if filepath.Base(x) == filepath.Base(y) { // (optimisation)
if xi, err := os.Stat(x); err == nil {
if yi, err := os.Stat(y); err == nil {
return os.SameFile(xi, yi)
}
}
}
return false
}
================================================
FILE: go/buildutil/util_test.go
================================================
// Copyright 2014 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package buildutil_test
import (
"go/build"
"os"
"path/filepath"
"runtime"
"strings"
"testing"
"golang.org/x/tools/go/buildutil"
"golang.org/x/tools/internal/packagestest"
)
func TestContainingPackage(t *testing.T) {
if runtime.Compiler == "gccgo" {
t.Skip("gccgo has no GOROOT")
}
exported := packagestest.Export(t, packagestest.GOPATH, []packagestest.Module{
{Name: "golang.org/x/tools/go/buildutil", Files: packagestest.MustCopyFileTree(".")}})
defer exported.Cleanup()
goroot := runtime.GOROOT()
var gopath string
for _, env := range exported.Config.Env {
if !strings.HasPrefix(env, "GOPATH=") {
continue
}
gopath = strings.TrimPrefix(env, "GOPATH=")
}
if gopath == "" {
t.Fatal("Failed to fish GOPATH out of env: ", exported.Config.Env)
}
buildutildir := filepath.Join(gopath, "golang.org", "x", "tools", "go", "buildutil")
type Test struct {
gopath, filename, wantPkg string
}
tests := []Test{
{gopath, goroot + "/src/fmt/print.go", "fmt"},
{gopath, goroot + "/src/encoding/json/foo.go", "encoding/json"},
{gopath, goroot + "/src/encoding/missing/foo.go", "(not found)"},
{gopath, gopath + "/src/golang.org/x/tools/go/buildutil/util_test.go",
"golang.org/x/tools/go/buildutil"},
}
if runtime.GOOS != "windows" && runtime.GOOS != "plan9" {
// Make a symlink to gopath for test
tmp, err := os.MkdirTemp(os.TempDir(), "go")
if err != nil {
t.Errorf("Unable to create a temporary directory in %s", os.TempDir())
}
defer os.RemoveAll(tmp)
// symlink between $GOPATH/src and /tmp/go/src
// in order to test all possible symlink cases
if err := os.Symlink(gopath+"/src", tmp+"/src"); err != nil {
t.Fatal(err)
}
tests = append(tests, []Test{
{gopath, tmp + "/src/golang.org/x/tools/go/buildutil/util_test.go", "golang.org/x/tools/go/buildutil"},
{tmp, gopath + "/src/golang.org/x/tools/go/buildutil/util_test.go", "golang.org/x/tools/go/buildutil"},
{tmp, tmp + "/src/golang.org/x/tools/go/buildutil/util_test.go", "golang.org/x/tools/go/buildutil"},
}...)
}
for _, test := range tests {
var got string
var buildContext = build.Default
buildContext.GOPATH = test.gopath
bp, err := buildutil.ContainingPackage(&buildContext, buildutildir, test.filename)
if err != nil {
got = "(not found)"
} else {
got = bp.ImportPath
}
if got != test.wantPkg {
t.Errorf("ContainingPackage(%q) = %s, want %s", test.filename, got, test.wantPkg)
}
}
}
================================================
FILE: go/buildutil/util_windows_test.go
================================================
// Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package buildutil_test
import (
"fmt"
"go/build"
"path/filepath"
"runtime"
"strings"
"testing"
"golang.org/x/tools/go/buildutil"
)
func testContainingPackageCaseFold(file, want string) error {
bp, err := buildutil.ContainingPackage(&build.Default, ".", file)
if err != nil {
return err
}
if got := bp.ImportPath; got != want {
return fmt.Errorf("ContainingPackage(%q) = %s, want %s", file, got, want)
}
return nil
}
func TestContainingPackageCaseFold(t *testing.T) {
path := filepath.Join(runtime.GOROOT(), `src\fmt\print.go`)
err := testContainingPackageCaseFold(path, "fmt")
if err != nil {
t.Error(err)
}
vol := filepath.VolumeName(path)
if len(vol) != 2 || vol[1] != ':' {
t.Fatalf("GOROOT path has unexpected volume name: %v", vol)
}
rest := path[len(vol):]
err = testContainingPackageCaseFold(strings.ToUpper(vol)+rest, "fmt")
if err != nil {
t.Error(err)
}
err = testContainingPackageCaseFold(strings.ToLower(vol)+rest, "fmt")
if err != nil {
t.Error(err)
}
}
================================================
FILE: go/callgraph/callgraph.go
================================================
// Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
/*
Package callgraph defines the call graph and various algorithms
and utilities to operate on it.
A call graph is a labelled directed graph whose nodes represent
functions and whose edge labels represent syntactic function call
sites. The presence of a labelled edge (caller, site, callee)
indicates that caller may call callee at the specified call site.
A call graph is a multigraph: it may contain multiple edges (caller,
*, callee) connecting the same pair of nodes, so long as the edges
differ by label; this occurs when one function calls another function
from multiple call sites. Also, it may contain multiple edges
(caller, site, *) that differ only by callee; this indicates a
polymorphic call.
A SOUND call graph is one that overapproximates the dynamic calling
behaviors of the program in all possible executions. One call graph
is more PRECISE than another if it is a smaller overapproximation of
the dynamic behavior.
All call graphs have a synthetic root node which is responsible for
calling main() and init().
Calls to built-in functions (e.g. panic, println) are not represented
in the call graph; they are treated like built-in operators of the
language.
*/
package callgraph // import "golang.org/x/tools/go/callgraph"
// TODO(zpavlinovic): decide how callgraphs handle calls to and from generic function bodies.
import (
"fmt"
"go/token"
"golang.org/x/tools/go/ssa"
)
// A Graph represents a call graph.
//
// A graph may contain nodes that are not reachable from the root.
// If the call graph is sound, such nodes indicate unreachable
// functions.
type Graph struct {
Root *Node // the distinguished root node (Root.Func may be nil)
Nodes map[*ssa.Function]*Node // all nodes by function
}
// New returns a new Graph with the specified (optional) root node.
func New(root *ssa.Function) *Graph {
g := &Graph{Nodes: make(map[*ssa.Function]*Node)}
g.Root = g.CreateNode(root)
return g
}
// CreateNode returns the Node for fn, creating it if not present.
// The root node may have fn=nil.
func (g *Graph) CreateNode(fn *ssa.Function) *Node {
n, ok := g.Nodes[fn]
if !ok {
n = &Node{Func: fn, ID: len(g.Nodes)}
g.Nodes[fn] = n
}
return n
}
// A Node represents a node in a call graph.
type Node struct {
Func *ssa.Function // the function this node represents
ID int // 0-based sequence number
In []*Edge // unordered set of incoming call edges (n.In[*].Callee == n)
Out []*Edge // unordered set of outgoing call edges (n.Out[*].Caller == n)
}
func (n *Node) String() string {
return fmt.Sprintf("n%d:%s", n.ID, n.Func)
}
// A Edge represents an edge in the call graph.
//
// Site is nil for edges originating in synthetic or intrinsic
// functions, e.g. reflect.Value.Call or the root of the call graph.
type Edge struct {
Caller *Node
Site ssa.CallInstruction
Callee *Node
}
func (e Edge) String() string {
return fmt.Sprintf("%s --> %s", e.Caller, e.Callee)
}
func (e Edge) Description() string {
var prefix string
switch e.Site.(type) {
case nil:
return "synthetic call"
case *ssa.Go:
prefix = "concurrent "
case *ssa.Defer:
prefix = "deferred "
}
return prefix + e.Site.Common().Description()
}
func (e Edge) Pos() token.Pos {
if e.Site == nil {
return token.NoPos
}
return e.Site.Pos()
}
// AddEdge adds the edge (caller, site, callee) to the call graph.
// Elimination of duplicate edges is the caller's responsibility.
func AddEdge(caller *Node, site ssa.CallInstruction, callee *Node) {
e := &Edge{caller, site, callee}
callee.In = append(callee.In, e)
caller.Out = append(caller.Out, e)
}
================================================
FILE: go/callgraph/callgraph_test.go
================================================
// Copyright 2023 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package callgraph_test
import (
"sync"
"testing"
"golang.org/x/tools/go/callgraph"
"golang.org/x/tools/go/callgraph/cha"
"golang.org/x/tools/go/callgraph/rta"
"golang.org/x/tools/go/callgraph/static"
"golang.org/x/tools/go/callgraph/vta"
"golang.org/x/tools/go/ssa"
"golang.org/x/tools/go/ssa/ssautil"
"golang.org/x/tools/internal/testfiles"
"golang.org/x/tools/txtar"
)
// Benchmarks comparing different callgraph algorithms implemented in
// x/tools/go/callgraph. Comparison is on both speed, memory and precision.
// Fewer edges and fewer reachable nodes implies a more precise result.
// Comparison is done on a hello world http server using net/http.
//
// Current results were on an i7 macbook on go version devel go1.20-2730.
// Number of nodes, edges, and reachable function are expected to vary between
// go versions. Timing results are expected to vary between machines.
// BenchmarkStatic-12 53 ms/op 6 MB/op 12113 nodes 37355 edges 1522 reachable
// BenchmarkCHA-12 86 ms/op 16 MB/op 12113 nodes 131717 edges 7640 reachable
// BenchmarkRTA-12 110 ms/op 12 MB/op 6566 nodes 42291 edges 5099 reachable
// BenchmarkPTA-12 1427 ms/op 600 MB/op 8714 nodes 28244 edges 4184 reachable
// BenchmarkVTA-12 600 ms/op 78 MB/op 12114 nodes 44861 edges 4919 reachable
// BenchmarkVTA2-12 793 ms/op 104 MB/op 5450 nodes 22208 edges 4042 reachable
// BenchmarkVTA3-12 977 ms/op 124 MB/op 4621 nodes 19331 edges 3700 reachable
// BenchmarkVTAAlt-12 372 ms/op 57 MB/op 7763 nodes 29912 edges 4258 reachable
// BenchmarkVTAAlt2-12 570 ms/op 78 MB/op 4838 nodes 20169 edges 3737 reachable
//
// Note:
// * Static is unsound and may miss real edges.
// * RTA starts from a main function and only includes reachable functions.
// * CHA starts from all functions.
// * VTA, VTA2, and VTA3 are starting from all functions and the CHA callgraph.
// VTA2 and VTA3 are the result of re-applying VTA to the functions reachable
// from main() via the callgraph of the previous stage.
// * VTAAlt, and VTAAlt2 start from the functions reachable from main via the
// CHA callgraph.
// * All algorithms are unsound w.r.t. reflection.
const httpEx = `
-- go.mod --
module x.io
-- main.go --
package main
import (
"fmt"
"net/http"
)
func hello(w http.ResponseWriter, req *http.Request) {
fmt.Fprintf(w, "hello world\n")
}
func main() {
http.HandleFunc("/hello", hello)
http.ListenAndServe(":8090", nil)
}
`
var (
once sync.Once
main *ssa.Function
)
func example(t testing.TB) (*ssa.Program, *ssa.Function) {
once.Do(func() {
pkgs := testfiles.LoadPackages(t, txtar.Parse([]byte(httpEx)), ".")
prog, ssapkgs := ssautil.Packages(pkgs, ssa.InstantiateGenerics)
prog.Build()
main = ssapkgs[0].Members["main"].(*ssa.Function)
})
return main.Prog, main
}
var stats bool = false // print stats?
func logStats(b *testing.B, cnd bool, name string, cg *callgraph.Graph, main *ssa.Function) {
if cnd && stats {
e := 0
for _, n := range cg.Nodes {
e += len(n.Out)
}
r := len(reaches(main, cg, false))
b.Logf("%s:\t%d nodes\t%d edges\t%d reachable", name, len(cg.Nodes), e, r)
}
}
func BenchmarkStatic(b *testing.B) {
prog, main := example(b)
for i := 0; b.Loop(); i++ {
cg := static.CallGraph(prog)
logStats(b, i == 0, "static", cg, main)
}
}
func BenchmarkCHA(b *testing.B) {
prog, main := example(b)
for i := 0; b.Loop(); i++ {
cg := cha.CallGraph(prog)
logStats(b, i == 0, "cha", cg, main)
}
}
func BenchmarkRTA(b *testing.B) {
_, main := example(b)
for i := 0; b.Loop(); i++ {
res := rta.Analyze([]*ssa.Function{main}, true)
cg := res.CallGraph
logStats(b, i == 0, "rta", cg, main)
}
}
func BenchmarkVTA(b *testing.B) {
prog, main := example(b)
for i := 0; b.Loop(); i++ {
cg := vta.CallGraph(ssautil.AllFunctions(prog), cha.CallGraph(prog))
logStats(b, i == 0, "vta", cg, main)
}
}
func BenchmarkVTA2(b *testing.B) {
prog, main := example(b)
for i := 0; b.Loop(); i++ {
vta1 := vta.CallGraph(ssautil.AllFunctions(prog), cha.CallGraph(prog))
cg := vta.CallGraph(reaches(main, vta1, true), vta1)
logStats(b, i == 0, "vta2", cg, main)
}
}
func BenchmarkVTA3(b *testing.B) {
prog, main := example(b)
for i := 0; b.Loop(); i++ {
vta1 := vta.CallGraph(ssautil.AllFunctions(prog), cha.CallGraph(prog))
vta2 := vta.CallGraph(reaches(main, vta1, true), vta1)
cg := vta.CallGraph(reaches(main, vta2, true), vta2)
logStats(b, i == 0, "vta3", cg, main)
}
}
func BenchmarkVTAAlt(b *testing.B) {
prog, main := example(b)
for i := 0; b.Loop(); i++ {
cha := cha.CallGraph(prog)
cg := vta.CallGraph(reaches(main, cha, true), cha) // start from only functions reachable by CHA.
logStats(b, i == 0, "vta-alt", cg, main)
}
}
func BenchmarkVTAAlt2(b *testing.B) {
prog, main := example(b)
for i := 0; b.Loop(); i++ {
cha := cha.CallGraph(prog)
vta1 := vta.CallGraph(reaches(main, cha, true), cha)
cg := vta.CallGraph(reaches(main, vta1, true), vta1)
logStats(b, i == 0, "vta-alt2", cg, main)
}
}
// reaches computes the transitive closure of functions forward reachable
// via calls in cg starting from `sources`. If refs is true, include
// functions referred to in an instruction.
func reaches(source *ssa.Function, cg *callgraph.Graph, refs bool) map[*ssa.Function]bool {
seen := make(map[*ssa.Function]bool)
var visit func(f *ssa.Function)
visit = func(f *ssa.Function) {
if seen[f] {
return
}
seen[f] = true
if n := cg.Nodes[f]; n != nil {
for _, e := range n.Out {
if e.Site != nil {
visit(e.Callee.Func)
}
}
}
if refs {
var buf [10]*ssa.Value // avoid alloc in common case
for _, b := range f.Blocks {
for _, instr := range b.Instrs {
for _, op := range instr.Operands(buf[:0]) {
if fn, ok := (*op).(*ssa.Function); ok {
visit(fn)
}
}
}
}
}
}
visit(source)
return seen
}
================================================
FILE: go/callgraph/cha/cha.go
================================================
// Copyright 2014 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package cha computes the call graph of a Go program using the Class
// Hierarchy Analysis (CHA) algorithm.
//
// CHA was first described in "Optimization of Object-Oriented Programs
// Using Static Class Hierarchy Analysis", Jeffrey Dean, David Grove,
// and Craig Chambers, ECOOP'95.
//
// CHA is related to RTA (see go/callgraph/rta); the difference is that
// CHA conservatively computes the entire "implements" relation between
// interfaces and concrete types ahead of time, whereas RTA uses dynamic
// programming to construct it on the fly as it encounters new functions
// reachable from main. CHA may thus include spurious call edges for
// types that haven't been instantiated yet, or types that are never
// instantiated.
//
// Since CHA conservatively assumes that all functions are address-taken
// and all concrete types are put into interfaces, it is sound to run on
// partial programs, such as libraries without a main or test function.
package cha // import "golang.org/x/tools/go/callgraph/cha"
// TODO(zpavlinovic): update CHA for how it handles generic function bodies.
import (
"golang.org/x/tools/go/callgraph"
"golang.org/x/tools/go/callgraph/internal/chautil"
"golang.org/x/tools/go/ssa"
"golang.org/x/tools/go/ssa/ssautil"
)
// CallGraph computes the call graph of the specified program using the
// Class Hierarchy Analysis algorithm.
func CallGraph(prog *ssa.Program) *callgraph.Graph {
cg := callgraph.New(nil) // TODO(adonovan) eliminate concept of rooted callgraph
allFuncs := ssautil.AllFunctions(prog)
calleesOf := lazyCallees(allFuncs)
addEdge := func(fnode *callgraph.Node, site ssa.CallInstruction, g *ssa.Function) {
gnode := cg.CreateNode(g)
callgraph.AddEdge(fnode, site, gnode)
}
addEdges := func(fnode *callgraph.Node, site ssa.CallInstruction, callees []*ssa.Function) {
// Because every call to a highly polymorphic and
// frequently used abstract method such as
// (io.Writer).Write is assumed to call every concrete
// Write method in the program, the call graph can
// contain a lot of duplication.
for _, g := range callees {
addEdge(fnode, site, g)
}
}
for f := range allFuncs {
fnode := cg.CreateNode(f)
for _, b := range f.Blocks {
for _, instr := range b.Instrs {
if site, ok := instr.(ssa.CallInstruction); ok {
if g := site.Common().StaticCallee(); g != nil {
addEdge(fnode, site, g)
} else {
addEdges(fnode, site, calleesOf(site))
}
}
}
}
}
return cg
}
var lazyCallees = chautil.LazyCallees
================================================
FILE: go/callgraph/cha/cha_test.go
================================================
// Copyright 2014 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// No testdata on Android.
//go:build !android
package cha_test
import (
"bytes"
"fmt"
"go/ast"
"go/token"
"go/types"
"os"
"path/filepath"
"sort"
"strings"
"testing"
"golang.org/x/tools/go/callgraph"
"golang.org/x/tools/go/callgraph/cha"
"golang.org/x/tools/go/packages"
"golang.org/x/tools/go/ssa"
"golang.org/x/tools/go/ssa/ssautil"
"golang.org/x/tools/internal/testenv"
"golang.org/x/tools/internal/testfiles"
"golang.org/x/tools/txtar"
)
var inputs = []string{
"testdata/func.go",
"testdata/iface.go",
"testdata/recv.go",
"testdata/issue23925.go",
}
func expectation(f *ast.File) (string, token.Pos) {
for _, c := range f.Comments {
text := strings.TrimSpace(c.Text())
if t, ok := strings.CutPrefix(text, "WANT:\n"); ok {
return t, c.Pos()
}
}
return "", token.NoPos
}
// TestCHA runs CHA on each file in inputs, prints the dynamic edges of
// the call graph, and compares it with the golden results embedded in
// the WANT comment at the end of the file.
func TestCHA(t *testing.T) {
for _, filename := range inputs {
pkg, ssapkg := loadFile(t, filename, ssa.InstantiateGenerics)
want, pos := expectation(pkg.Syntax[0])
if pos == token.NoPos {
t.Error(fmt.Errorf("No WANT: comment in %s", filename))
continue
}
cg := cha.CallGraph(ssapkg.Prog)
if got := printGraph(cg, pkg.Types, "dynamic", "Dynamic calls"); got != want {
t.Errorf("%s: got:\n%s\nwant:\n%s",
ssapkg.Prog.Fset.Position(pos), got, want)
}
}
}
// TestCHAGenerics is TestCHA tailored for testing generics,
func TestCHAGenerics(t *testing.T) {
filename := "testdata/generics.go"
pkg, ssapkg := loadFile(t, filename, ssa.InstantiateGenerics)
want, pos := expectation(pkg.Syntax[0])
if pos == token.NoPos {
t.Fatal(fmt.Errorf("No WANT: comment in %s", filename))
}
cg := cha.CallGraph(ssapkg.Prog)
if got := printGraph(cg, pkg.Types, "", "All calls"); got != want {
t.Errorf("%s: got:\n%s\nwant:\n%s",
ssapkg.Prog.Fset.Position(pos), got, want)
}
}
// TestCHAUnexported tests call resolution for unexported methods.
func TestCHAUnexported(t *testing.T) {
// The two packages below each have types with methods called "m".
// Each of these methods should only be callable by functions in their
// own package, because they are unexported.
//
// In particular:
// - main.main can call (main.S1).m
// - p2.Foo can call (p2.S2).m
// - main.main cannot call (p2.S2).m
// - p2.Foo cannot call (main.S1).m
//
// We use CHA to build a callgraph, then check that it has the
// appropriate set of edges.
const src = `
-- go.mod --
module x.io
go 1.18
-- main/main.go --
package main
import "x.io/p2"
type I1 interface { m() }
type S1 struct { p2.I2 }
func (s S1) m() { }
func main() {
var s S1
var o I1 = s
o.m()
p2.Foo(s)
}
-- p2/p2.go --
package p2
type I2 interface { m() }
type S2 struct { }
func (s S2) m() { }
func Foo(i I2) { i.m() }
`
want := `All calls
x.io/main.init --> x.io/p2.init
x.io/main.main --> (x.io/main.S1).m
x.io/main.main --> x.io/p2.Foo
x.io/p2.Foo --> (x.io/p2.S2).m`
pkgs := testfiles.LoadPackages(t, txtar.Parse([]byte(src)), "./...")
prog, _ := ssautil.Packages(pkgs, ssa.InstantiateGenerics)
prog.Build()
cg := cha.CallGraph(prog)
// The graph is easier to read without synthetic nodes.
cg.DeleteSyntheticNodes()
if got := printGraph(cg, nil, "", "All calls"); got != want {
t.Errorf("cha.CallGraph: got:\n%s\nwant:\n%s", got, want)
}
}
// loadFile loads a built SSA package for a single-file "x.io/main" package.
// (Ideally all uses would be converted over to txtar files with explicit go.mod files.)
func loadFile(t testing.TB, filename string, mode ssa.BuilderMode) (*packages.Package, *ssa.Package) {
testenv.NeedsGoPackages(t)
data, err := os.ReadFile(filename)
if err != nil {
t.Fatal(err)
}
dir := t.TempDir()
cfg := &packages.Config{
Mode: packages.LoadAllSyntax,
Dir: dir,
Overlay: map[string][]byte{
filepath.Join(dir, "go.mod"): []byte("module x.io\ngo 1.22"),
filepath.Join(dir, "main/main.go"): data,
},
Env: append(os.Environ(), "GO111MODULES=on", "GOPATH=", "GOWORK=off", "GOPROXY=off"),
}
pkgs, err := packages.Load(cfg, "./main")
if err != nil {
t.Fatal(err)
}
if num := packages.PrintErrors(pkgs); num > 0 {
t.Fatalf("packages contained %d errors", num)
}
prog, ssapkgs := ssautil.Packages(pkgs, mode)
prog.Build()
return pkgs[0], ssapkgs[0]
}
// printGraph returns a string representation of cg involving only edges
// whose description contains edgeMatch. The string representation is
// prefixed with a desc line.
func printGraph(cg *callgraph.Graph, from *types.Package, edgeMatch string, desc string) string {
var edges []string
callgraph.GraphVisitEdges(cg, func(e *callgraph.Edge) error {
if strings.Contains(e.Description(), edgeMatch) {
edges = append(edges, fmt.Sprintf("%s --> %s",
e.Caller.Func.RelString(from),
e.Callee.Func.RelString(from)))
}
return nil
})
sort.Strings(edges)
var buf bytes.Buffer
buf.WriteString(desc + "\n")
for _, edge := range edges {
fmt.Fprintf(&buf, " %s\n", edge)
}
return strings.TrimSpace(buf.String())
}
================================================
FILE: go/callgraph/cha/testdata/func.go
================================================
package main
// Test of dynamic function calls; no interfaces.
func A(int) {}
var (
B = func(int) {}
C = func(int) {}
)
func f() {
pfn := B
pfn(0) // calls A, B, C, even though A is not even address-taken
}
// WANT:
// Dynamic calls
// f --> A
// f --> init$1
// f --> init$2
================================================
FILE: go/callgraph/cha/testdata/generics.go
================================================
package main
// Test of generic function calls.
type I interface {
Foo()
}
type A struct{}
func (a A) Foo() {}
type B struct{}
func (b B) Foo() {}
func instantiated[X I](x X) {
x.Foo()
}
func Bar() {}
func f(h func(), g func(I), k func(A), a A, b B) {
h()
k(a)
g(b) // g:func(I) is not matched by instantiated[B]:func(B)
instantiated[A](a) // static call
instantiated[B](b) // static call
}
// WANT:
// All calls
// (*A).Foo --> (A).Foo
// (*B).Foo --> (B).Foo
// f --> Bar
// f --> instantiated[x.io/main.A]
// f --> instantiated[x.io/main.A]
// f --> instantiated[x.io/main.B]
// instantiated --> (*A).Foo
// instantiated --> (*B).Foo
// instantiated --> (A).Foo
// instantiated --> (B).Foo
// instantiated[x.io/main.A] --> (A).Foo
// instantiated[x.io/main.B] --> (B).Foo
================================================
FILE: go/callgraph/cha/testdata/iface.go
================================================
package main
// Test of interface calls. None of the concrete types are ever
// instantiated or converted to interfaces.
type I interface {
f()
}
type J interface {
f()
g()
}
type C int // implements I
func (*C) f()
type D int // implements I and J
func (*D) f()
func (*D) g()
func one(i I, j J) {
i.f() // calls *C and *D
}
func two(i I, j J) {
j.f() // calls *D (but not *C, even though it defines method f)
}
func three(i I, j J) {
j.g() // calls *D
}
func four(i I, j J) {
Jf := J.f
if unknown {
Jf = nil // suppress SSA constant propagation
}
Jf(nil) // calls *D
}
func five(i I, j J) {
jf := j.f
if unknown {
jf = nil // suppress SSA constant propagation
}
jf() // calls *D
}
var unknown bool
// WANT:
// Dynamic calls
// (J).f$bound --> (*D).f
// (J).f$thunk --> (*D).f
// five --> (J).f$bound
// four --> (J).f$thunk
// one --> (*C).f
// one --> (*D).f
// three --> (*D).g
// two --> (*D).f
================================================
FILE: go/callgraph/cha/testdata/issue23925.go
================================================
package main
// Regression test for https://golang.org/issue/23925
type stringFlagImpl string
func (*stringFlagImpl) Set(s string) error { return nil }
type boolFlagImpl bool
func (*boolFlagImpl) Set(s string) error { return nil }
func (*boolFlagImpl) extra() {}
// A copy of flag.boolFlag interface, without a dependency.
// Must appear first, so that it becomes the owner of the Set methods.
type boolFlag interface {
flagValue
extra()
}
// A copy of flag.Value, without adding a dependency.
type flagValue interface {
Set(string) error
}
func main() {
var x flagValue = new(stringFlagImpl)
x.Set("")
var y boolFlag = new(boolFlagImpl)
y.Set("")
}
// WANT:
// Dynamic calls
// main --> (*boolFlagImpl).Set
// main --> (*boolFlagImpl).Set
// main --> (*stringFlagImpl).Set
================================================
FILE: go/callgraph/cha/testdata/recv.go
================================================
package main
type I interface {
f()
}
type J interface {
g()
}
type C int // C and *C implement I; *C implements J
func (C) f()
func (*C) g()
type D int // *D implements I and J
func (*D) f()
func (*D) g()
func f(i I) {
i.f() // calls C, *C, *D
}
func g(j J) {
j.g() // calls *C, *D
}
// WANT:
// Dynamic calls
// f --> (*C).f
// f --> (*D).f
// f --> (C).f
// g --> (*C).g
// g --> (*D).g
================================================
FILE: go/callgraph/internal/chautil/lazy.go
================================================
// Copyright 2024 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package chautil provides helper functions related to
// class hierarchy analysis (CHA) for use in x/tools.
package chautil
import (
"go/types"
"golang.org/x/tools/go/ssa"
"golang.org/x/tools/go/types/typeutil"
)
// LazyCallees returns a function that maps a call site (in a function in fns)
// to its callees within fns. The set of callees is computed using the CHA algorithm,
// i.e., on the entire implements relation between interfaces and concrete types
// in fns. Please see golang.org/x/tools/go/callgraph/cha for more information.
//
// The resulting function is not concurrency safe.
func LazyCallees(fns map[*ssa.Function]bool) func(site ssa.CallInstruction) []*ssa.Function {
// funcsBySig contains all functions, keyed by signature. It is
// the effective set of address-taken functions used to resolve
// a dynamic call of a particular signature.
var funcsBySig typeutil.Map // value is []*ssa.Function
// methodsByID contains all methods, grouped by ID for efficient
// lookup.
//
// We must key by ID, not name, for correct resolution of interface
// calls to a type with two (unexported) methods spelled the same but
// from different packages. The fact that the concrete type implements
// the interface does not mean the call dispatches to both methods.
methodsByID := make(map[string][]*ssa.Function)
// An imethod represents an interface method I.m.
// (There's no go/types object for it;
// a *types.Func may be shared by many interfaces due to interface embedding.)
type imethod struct {
I *types.Interface
id string
}
// methodsMemo records, for every abstract method call I.m on
// interface type I, the set of concrete methods C.m of all
// types C that satisfy interface I.
//
// Abstract methods may be shared by several interfaces,
// hence we must pass I explicitly, not guess from m.
//
// methodsMemo is just a cache, so it needn't be a typeutil.Map.
methodsMemo := make(map[imethod][]*ssa.Function)
lookupMethods := func(I *types.Interface, m *types.Func) []*ssa.Function {
id := m.Id()
methods, ok := methodsMemo[imethod{I, id}]
if !ok {
for _, f := range methodsByID[id] {
C := f.Signature.Recv().Type() // named or *named
if types.Implements(C, I) {
methods = append(methods, f)
}
}
methodsMemo[imethod{I, id}] = methods
}
return methods
}
for f := range fns {
if f.Signature.Recv() == nil {
// Package initializers can never be address-taken.
if f.Name() == "init" && f.Synthetic == "package initializer" {
continue
}
funcs, _ := funcsBySig.At(f.Signature).([]*ssa.Function)
funcs = append(funcs, f)
funcsBySig.Set(f.Signature, funcs)
} else if obj := f.Object(); obj != nil {
id := obj.(*types.Func).Id()
methodsByID[id] = append(methodsByID[id], f)
}
}
return func(site ssa.CallInstruction) []*ssa.Function {
call := site.Common()
if call.IsInvoke() {
tiface := call.Value.Type().Underlying().(*types.Interface)
return lookupMethods(tiface, call.Method)
} else if g := call.StaticCallee(); g != nil {
return []*ssa.Function{g}
} else if _, ok := call.Value.(*ssa.Builtin); !ok {
fns, _ := funcsBySig.At(call.Signature()).([]*ssa.Function)
return fns
}
return nil
}
}
================================================
FILE: go/callgraph/rta/rta.go
================================================
// Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// This package provides Rapid Type Analysis (RTA) for Go, a fast
// algorithm for call graph construction and discovery of reachable code
// (and hence dead code) and runtime types. The algorithm was first
// described in:
//
// David F. Bacon and Peter F. Sweeney. 1996.
// Fast static analysis of C++ virtual function calls. (OOPSLA '96)
// http://doi.acm.org/10.1145/236337.236371
//
// The algorithm uses dynamic programming to tabulate the cross-product
// of the set of known "address-taken" functions with the set of known
// dynamic calls of the same type. As each new address-taken function
// is discovered, call graph edges are added from each known callsite,
// and as each new call site is discovered, call graph edges are added
// from it to each known address-taken function.
//
// A similar approach is used for dynamic calls via interfaces: it
// tabulates the cross-product of the set of known "runtime types",
// i.e. types that may appear in an interface value, or may be derived from
// one via reflection, with the set of known "invoke"-mode dynamic
// calls. As each new runtime type is discovered, call edges are
// added from the known call sites, and as each new call site is
// discovered, call graph edges are added to each compatible
// method.
//
// In addition, we must consider as reachable all address-taken
// functions and all exported methods of any runtime type, since they
// may be called via reflection.
//
// Each time a newly added call edge causes a new function to become
// reachable, the code of that function is analyzed for more call sites,
// address-taken functions, and runtime types. The process continues
// until a fixed point is reached.
package rta // import "golang.org/x/tools/go/callgraph/rta"
import (
"fmt"
"go/types"
"hash/crc32"
"golang.org/x/tools/go/callgraph"
"golang.org/x/tools/go/ssa"
"golang.org/x/tools/go/types/typeutil"
)
// A Result holds the results of Rapid Type Analysis, which includes the
// set of reachable functions/methods, runtime types, and the call graph.
type Result struct {
// CallGraph is the discovered callgraph.
// It does not include edges for calls made via reflection.
CallGraph *callgraph.Graph
// Reachable contains the set of reachable functions and methods.
// This includes exported methods of runtime types, since
// they may be accessed via reflection.
// The value indicates whether the function is address-taken.
//
// (We wrap the bool in a struct to avoid inadvertent use of
// "if Reachable[f] {" to test for set membership.)
Reachable map[*ssa.Function]struct{ AddrTaken bool }
// RuntimeTypes contains the set of types that are needed at
// runtime, for interfaces or reflection.
//
// The value indicates whether the type is inaccessible to reflection.
// Consider:
// type A struct{B}
// fmt.Println(new(A))
// Types *A, A and B are accessible to reflection, but the unnamed
// type struct{B} is not.
RuntimeTypes typeutil.Map
}
// Working state of the RTA algorithm.
type rta struct {
result *Result
prog *ssa.Program
reflectValueCall *ssa.Function // (*reflect.Value).Call, iff part of prog
worklist []*ssa.Function // list of functions to visit
// addrTakenFuncsBySig contains all address-taken *Functions, grouped by signature.
// Keys are *types.Signature, values are map[*ssa.Function]bool sets.
addrTakenFuncsBySig typeutil.Map
// dynCallSites contains all dynamic "call"-mode call sites, grouped by signature.
// Keys are *types.Signature, values are unordered []ssa.CallInstruction.
dynCallSites typeutil.Map
// invokeSites contains all "invoke"-mode call sites, grouped by interface.
// Keys are *types.Interface (never *types.Named),
// Values are unordered []ssa.CallInstruction sets.
invokeSites typeutil.Map
// The following two maps together define the subset of the
// m:n "implements" relation needed by the algorithm.
// concreteTypes maps each concrete type to information about it.
// Keys are types.Type, values are *concreteTypeInfo.
// Only concrete types used as MakeInterface operands are included.
concreteTypes typeutil.Map
// interfaceTypes maps each interface type to information about it.
// Keys are *types.Interface, values are *interfaceTypeInfo.
// Only interfaces used in "invoke"-mode CallInstructions are included.
interfaceTypes typeutil.Map
}
type concreteTypeInfo struct {
C types.Type
mset *types.MethodSet
fprint uint64 // fingerprint of method set
implements []*types.Interface // unordered set of implemented interfaces
}
type interfaceTypeInfo struct {
I *types.Interface
mset *types.MethodSet
fprint uint64
implementations []types.Type // unordered set of concrete implementations
}
// addReachable marks a function as potentially callable at run-time,
// and ensures that it gets processed.
func (r *rta) addReachable(f *ssa.Function, addrTaken bool) {
reachable := r.result.Reachable
n := len(reachable)
v := reachable[f]
if addrTaken {
v.AddrTaken = true
}
reachable[f] = v
if len(reachable) > n {
// First time seeing f. Add it to the worklist.
r.worklist = append(r.worklist, f)
}
}
// addEdge adds the specified call graph edge, and marks it reachable.
// addrTaken indicates whether to mark the callee as "address-taken".
// site is nil for calls made via reflection.
func (r *rta) addEdge(caller *ssa.Function, site ssa.CallInstruction, callee *ssa.Function, addrTaken bool) {
r.addReachable(callee, addrTaken)
if g := r.result.CallGraph; g != nil {
if caller == nil {
panic(site)
}
from := g.CreateNode(caller)
to := g.CreateNode(callee)
callgraph.AddEdge(from, site, to)
}
}
// ---------- addrTakenFuncs × dynCallSites ----------
// visitAddrTakenFunc is called each time we encounter an address-taken function f.
func (r *rta) visitAddrTakenFunc(f *ssa.Function) {
// Create two-level map (Signature -> Function -> bool).
S := f.Signature
funcs, _ := r.addrTakenFuncsBySig.At(S).(map[*ssa.Function]bool)
if funcs == nil {
funcs = make(map[*ssa.Function]bool)
r.addrTakenFuncsBySig.Set(S, funcs)
}
if !funcs[f] {
// First time seeing f.
funcs[f] = true
// If we've seen any dyncalls of this type, mark it reachable,
// and add call graph edges.
sites, _ := r.dynCallSites.At(S).([]ssa.CallInstruction)
for _, site := range sites {
r.addEdge(site.Parent(), site, f, true)
}
// If the program includes (*reflect.Value).Call,
// add a dynamic call edge from it to any address-taken
// function, regardless of signature.
//
// This isn't perfect.
// - The actual call comes from an internal function
// called reflect.call, but we can't rely on that here.
// - reflect.Value.CallSlice behaves similarly,
// but we don't bother to create callgraph edges from
// it as well as it wouldn't fundamentally change the
// reachability but it would add a bunch more edges.
// - We assume that if reflect.Value.Call is among
// the dependencies of the application, it is itself
// reachable. (It would be more accurate to defer
// all the addEdges below until r.V.Call itself
// becomes reachable.)
// - Fake call graph edges are added from r.V.Call to
// each address-taken function, but not to every
// method reachable through a materialized rtype,
// which is a little inconsistent. Still, the
// reachable set includes both kinds, which is what
// matters for e.g. deadcode detection.)
if r.reflectValueCall != nil {
var site ssa.CallInstruction = nil // can't find actual call site
r.addEdge(r.reflectValueCall, site, f, true)
}
}
}
// visitDynCall is called each time we encounter a dynamic "call"-mode call.
func (r *rta) visitDynCall(site ssa.CallInstruction) {
S := site.Common().Signature()
// Record the call site.
sites, _ := r.dynCallSites.At(S).([]ssa.CallInstruction)
r.dynCallSites.Set(S, append(sites, site))
// For each function of signature S that we know is address-taken,
// add an edge and mark it reachable.
funcs, _ := r.addrTakenFuncsBySig.At(S).(map[*ssa.Function]bool)
for g := range funcs {
r.addEdge(site.Parent(), site, g, true)
}
}
// ---------- concrete types × invoke sites ----------
// addInvokeEdge is called for each new pair (site, C) in the matrix.
func (r *rta) addInvokeEdge(site ssa.CallInstruction, C types.Type) {
// Ascertain the concrete method of C to be called.
imethod := site.Common().Method
cmethod := r.prog.LookupMethod(C, imethod.Pkg(), imethod.Name())
r.addEdge(site.Parent(), site, cmethod, true)
}
// visitInvoke is called each time the algorithm encounters an "invoke"-mode call.
func (r *rta) visitInvoke(site ssa.CallInstruction) {
I := site.Common().Value.Type().Underlying().(*types.Interface)
// Record the invoke site.
sites, _ := r.invokeSites.At(I).([]ssa.CallInstruction)
r.invokeSites.Set(I, append(sites, site))
// Add callgraph edge for each existing
// address-taken concrete type implementing I.
for _, C := range r.implementations(I) {
r.addInvokeEdge(site, C)
}
}
// ---------- main algorithm ----------
// visitFunc processes function f.
func (r *rta) visitFunc(f *ssa.Function) {
var space [32]*ssa.Value // preallocate space for common case
for _, b := range f.Blocks {
for _, instr := range b.Instrs {
rands := instr.Operands(space[:0])
switch instr := instr.(type) {
case ssa.CallInstruction:
call := instr.Common()
if call.IsInvoke() {
r.visitInvoke(instr)
} else if g := call.StaticCallee(); g != nil {
r.addEdge(f, instr, g, false)
} else if _, ok := call.Value.(*ssa.Builtin); !ok {
r.visitDynCall(instr)
}
// Ignore the call-position operand when
// looking for address-taken Functions.
// Hack: assume this is rands[0].
rands = rands[1:]
case *ssa.MakeInterface:
// Converting a value of type T to an
// interface materializes its runtime
// type, allowing any of its exported
// methods to be called though reflection.
r.addRuntimeType(instr.X.Type(), false)
}
// Process all address-taken functions.
for _, op := range rands {
if g, ok := (*op).(*ssa.Function); ok {
r.visitAddrTakenFunc(g)
}
}
}
}
}
// Analyze performs Rapid Type Analysis, starting at the specified root
// functions. It returns nil if no roots were specified.
//
// The root functions must be one or more entrypoints (main and init
// functions) of a complete SSA program, with function bodies for all
// dependencies, constructed with the [ssa.InstantiateGenerics] mode
// flag.
//
// If buildCallGraph is true, Result.CallGraph will contain a call
// graph; otherwise, only the other fields (reachable functions) are
// populated.
func Analyze(roots []*ssa.Function, buildCallGraph bool) *Result {
if len(roots) == 0 {
return nil
}
r := &rta{
result: &Result{Reachable: make(map[*ssa.Function]struct{ AddrTaken bool })},
prog: roots[0].Prog,
}
if buildCallGraph {
// TODO(adonovan): change callgraph API to eliminate the
// notion of a distinguished root node. Some callgraphs
// have many roots, or none.
r.result.CallGraph = callgraph.New(roots[0])
}
// Grab ssa.Function for (*reflect.Value).Call,
// if "reflect" is among the dependencies.
if reflectPkg := r.prog.ImportedPackage("reflect"); reflectPkg != nil {
reflectValue := reflectPkg.Members["Value"].(*ssa.Type)
r.reflectValueCall = r.prog.LookupMethod(reflectValue.Object().Type(), reflectPkg.Pkg, "Call")
}
hasher := typeutil.MakeHasher()
r.result.RuntimeTypes.SetHasher(hasher)
r.addrTakenFuncsBySig.SetHasher(hasher)
r.dynCallSites.SetHasher(hasher)
r.invokeSites.SetHasher(hasher)
r.concreteTypes.SetHasher(hasher)
r.interfaceTypes.SetHasher(hasher)
for _, root := range roots {
r.addReachable(root, false)
}
// Visit functions, processing their instructions, and adding
// new functions to the worklist, until a fixed point is
// reached.
var shadow []*ssa.Function // for efficiency, we double-buffer the worklist
for len(r.worklist) > 0 {
shadow, r.worklist = r.worklist, shadow[:0]
for _, f := range shadow {
r.visitFunc(f)
}
}
return r.result
}
// interfaces(C) returns all currently known interfaces implemented by C.
func (r *rta) interfaces(C types.Type) []*types.Interface {
// Create an info for C the first time we see it.
var cinfo *concreteTypeInfo
if v := r.concreteTypes.At(C); v != nil {
cinfo = v.(*concreteTypeInfo)
} else {
mset := r.prog.MethodSets.MethodSet(C)
cinfo = &concreteTypeInfo{
C: C,
mset: mset,
fprint: fingerprint(mset),
}
r.concreteTypes.Set(C, cinfo)
// Ascertain set of interfaces C implements
// and update the 'implements' relation.
r.interfaceTypes.Iterate(func(I types.Type, v any) {
iinfo := v.(*interfaceTypeInfo)
if I := types.Unalias(I).(*types.Interface); implements(cinfo, iinfo) {
iinfo.implementations = append(iinfo.implementations, C)
cinfo.implements = append(cinfo.implements, I)
}
})
}
return cinfo.implements
}
// implementations(I) returns all currently known concrete types that implement I.
func (r *rta) implementations(I *types.Interface) []types.Type {
// Create an info for I the first time we see it.
var iinfo *interfaceTypeInfo
if v := r.interfaceTypes.At(I); v != nil {
iinfo = v.(*interfaceTypeInfo)
} else {
mset := r.prog.MethodSets.MethodSet(I)
iinfo = &interfaceTypeInfo{
I: I,
mset: mset,
fprint: fingerprint(mset),
}
r.interfaceTypes.Set(I, iinfo)
// Ascertain set of concrete types that implement I
// and update the 'implements' relation.
r.concreteTypes.Iterate(func(C types.Type, v any) {
cinfo := v.(*concreteTypeInfo)
if implements(cinfo, iinfo) {
cinfo.implements = append(cinfo.implements, I)
iinfo.implementations = append(iinfo.implementations, C)
}
})
}
return iinfo.implementations
}
// addRuntimeType is called for each concrete type that can be the
// dynamic type of some interface or reflect.Value.
// Adapted from needMethods in go/ssa/builder.go
func (r *rta) addRuntimeType(T types.Type, skip bool) {
// Never record aliases.
T = types.Unalias(T)
if prev, ok := r.result.RuntimeTypes.At(T).(bool); ok {
if skip && !prev {
r.result.RuntimeTypes.Set(T, skip)
}
return
}
r.result.RuntimeTypes.Set(T, skip)
mset := r.prog.MethodSets.MethodSet(T)
if _, ok := T.Underlying().(*types.Interface); !ok {
// T is a new concrete type.
for i, n := 0, mset.Len(); i < n; i++ {
sel := mset.At(i)
m := sel.Obj()
if m.Exported() {
// Exported methods are always potentially callable via reflection.
r.addReachable(r.prog.MethodValue(sel), true)
}
}
// Add callgraph edge for each existing dynamic
// "invoke"-mode call via that interface.
for _, I := range r.interfaces(T) {
sites, _ := r.invokeSites.At(I).([]ssa.CallInstruction)
for _, site := range sites {
r.addInvokeEdge(site, T)
}
}
}
// Precondition: T is not a method signature (*Signature with Recv()!=nil).
// Recursive case: skip => don't call makeMethods(T).
// Each package maintains its own set of types it has visited.
var n *types.Named
switch T := types.Unalias(T).(type) {
case *types.Named:
n = T
case *types.Pointer:
n, _ = types.Unalias(T.Elem()).(*types.Named)
}
if n != nil {
owner := n.Obj().Pkg()
if owner == nil {
return // built-in error type
}
}
// Recursion over signatures of each exported method.
for method := range mset.Methods() {
if method.Obj().Exported() {
sig := method.Type().(*types.Signature)
r.addRuntimeType(sig.Params(), true) // skip the Tuple itself
r.addRuntimeType(sig.Results(), true) // skip the Tuple itself
}
}
switch t := T.(type) {
case *types.Alias:
panic("unreachable")
case *types.Basic:
// nop
case *types.Interface:
// nop---handled by recursion over method set.
case *types.Pointer:
r.addRuntimeType(t.Elem(), false)
case *types.Slice:
r.addRuntimeType(t.Elem(), false)
case *types.Chan:
r.addRuntimeType(t.Elem(), false)
case *types.Map:
r.addRuntimeType(t.Key(), false)
r.addRuntimeType(t.Elem(), false)
case *types.Signature:
if t.Recv() != nil {
panic(fmt.Sprintf("Signature %s has Recv %s", t, t.Recv()))
}
r.addRuntimeType(t.Params(), true) // skip the Tuple itself
r.addRuntimeType(t.Results(), true) // skip the Tuple itself
case *types.Named:
// A pointer-to-named type can be derived from a named
// type via reflection. It may have methods too.
r.addRuntimeType(types.NewPointer(T), false)
// Consider 'type T struct{S}' where S has methods.
// Reflection provides no way to get from T to struct{S},
// only to S, so the method set of struct{S} is unwanted,
// so set 'skip' flag during recursion.
r.addRuntimeType(t.Underlying(), true)
case *types.Array:
r.addRuntimeType(t.Elem(), false)
case *types.Struct:
for i, n := 0, t.NumFields(); i < n; i++ {
r.addRuntimeType(t.Field(i).Type(), false)
}
case *types.Tuple:
for i, n := 0, t.Len(); i < n; i++ {
r.addRuntimeType(t.At(i).Type(), false)
}
default:
panic(T)
}
}
// fingerprint returns a bitmask with one bit set per method id,
// enabling 'implements' to quickly reject most candidates.
func fingerprint(mset *types.MethodSet) uint64 {
var space [64]byte
var mask uint64
for method := range mset.Methods() {
method := method.Obj()
sig := method.Type().(*types.Signature)
sum := crc32.ChecksumIEEE(fmt.Appendf(space[:], "%s/%d/%d",
method.Id(),
sig.Params().Len(),
sig.Results().Len()))
mask |= 1 << (sum % 64)
}
return mask
}
// implements reports whether types.Implements(cinfo.C, iinfo.I),
// but more efficiently.
func implements(cinfo *concreteTypeInfo, iinfo *interfaceTypeInfo) (got bool) {
// The concrete type must have at least the methods
// (bits) of the interface type. Use a bitwise subset
// test to reject most candidates quickly.
return iinfo.fprint & ^cinfo.fprint == 0 && types.Implements(cinfo.C, iinfo.I)
}
================================================
FILE: go/callgraph/rta/rta_test.go
================================================
// Copyright 2014 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// No testdata on Android.
//go:build !android
package rta_test
import (
"fmt"
"go/ast"
"go/types"
"sort"
"strings"
"testing"
"golang.org/x/tools/go/callgraph"
"golang.org/x/tools/go/callgraph/rta"
"golang.org/x/tools/go/ssa"
"golang.org/x/tools/go/ssa/ssautil"
"golang.org/x/tools/internal/testfiles"
"golang.org/x/tools/txtar"
)
// TestRTA runs RTA on each testdata/*.txtar file containing a single
// go file in a single package or multiple files in different packages,
// and compares the results with the expectations expressed in the WANT
// comment.
func TestRTA(t *testing.T) {
archivePaths := []string{
"testdata/func.txtar",
"testdata/generics.txtar",
"testdata/iface.txtar",
"testdata/reflectcall.txtar",
"testdata/rtype.txtar",
"testdata/multipkgs.txtar",
}
for _, archive := range archivePaths {
t.Run(archive, func(t *testing.T) {
ar, err := txtar.ParseFile(archive)
if err != nil {
t.Fatal(err)
}
pkgs := testfiles.LoadPackages(t, ar, "./...")
// find the file which contains the expected result
var f *ast.File
for _, p := range pkgs {
// We assume the packages have a single file or
// the wanted result is in the first file of the main package.
if p.Name == "main" {
f = p.Syntax[0]
}
}
if f == nil {
t.Fatalf("failed to find the file with expected result within main package %s", archive)
}
prog, spkgs := ssautil.Packages(pkgs, ssa.SanityCheckFunctions|ssa.InstantiateGenerics)
// find the main package to get functions for rta analysis
var mainPkg *ssa.Package
for _, sp := range spkgs {
if sp.Pkg.Name() == "main" {
mainPkg = sp
break
}
}
if mainPkg == nil {
t.Fatalf("failed to find main ssa package %s", archive)
}
prog.Build()
res := rta.Analyze([]*ssa.Function{
mainPkg.Func("main"),
mainPkg.Func("init"),
}, true)
check(t, f, mainPkg, res)
})
}
}
// check tests the RTA analysis results against the test expectations
// defined by a comment starting with a line "WANT:".
//
// The rest of the comment consists of lines of the following forms:
//
// edge --kind--> # call graph edge
// reachable # reachable function
// rtype # run-time type descriptor needed
//
// Each line asserts that an element is found in the given set, or, if
// the line is preceded by "!", that it is not in the set.
//
// Functions are notated as if by ssa.Function.String.
func check(t *testing.T, f *ast.File, pkg *ssa.Package, res *rta.Result) {
tokFile := pkg.Prog.Fset.File(f.FileStart)
// Find the WANT comment.
expectation := func(f *ast.File) (string, int) {
for _, c := range f.Comments {
text := strings.TrimSpace(c.Text())
if t, ok := strings.CutPrefix(text, "WANT:\n"); ok {
return t, tokFile.Line(c.Pos())
}
}
t.Fatalf("No WANT: comment in %s", tokFile.Name())
return "", 0
}
want, linenum := expectation(f)
// Parse the comment into three string-to-sense maps.
var (
wantEdge = make(map[string]bool)
wantReachable = make(map[string]bool)
wantRtype = make(map[string]bool)
)
for line := range strings.SplitSeq(want, "\n") {
linenum++
orig := line
bad := func() {
t.Fatalf("%s:%d: invalid assertion: %q", tokFile.Name(), linenum, orig)
}
line := strings.TrimSpace(line)
if line == "" {
continue // skip blanks
}
// A leading "!" negates the assertion.
sense := true
if rest, ok := strings.CutPrefix(line, "!"); ok {
sense = false
line = strings.TrimSpace(rest)
if line == "" {
bad()
}
}
// Select the map.
var want map[string]bool
kind := strings.Fields(line)[0]
switch kind {
case "edge":
want = wantEdge
case "reachable":
want = wantReachable
case "rtype":
want = wantRtype
default:
bad()
}
// Add expectation.
str := strings.TrimSpace(line[len(kind):])
want[str] = sense
}
type stringset = map[string]bool // (sets: values are true)
// compare checks that got matches each assertion of the form
// (str, sense) in want. The sense determines whether the test
// is positive or negative.
compare := func(kind string, got stringset, want map[string]bool) {
ok := true
for str, sense := range want {
if got[str] != sense {
ok = false
if sense {
t.Errorf("missing %s %q", kind, str)
} else {
t.Errorf("unwanted %s %q", kind, str)
}
}
}
// Print the actual output in expectation form.
if !ok {
var strs []string
for s := range got {
strs = append(strs, s)
}
sort.Strings(strs)
var buf strings.Builder
for _, str := range strs {
fmt.Fprintf(&buf, "%s %s\n", kind, str)
}
t.Errorf("got:\n%s", &buf)
}
}
// Check call graph edges.
{
got := make(stringset)
callgraph.GraphVisitEdges(res.CallGraph, func(e *callgraph.Edge) error {
edge := fmt.Sprintf("%s --%s--> %s",
e.Caller.Func.RelString(pkg.Pkg),
e.Description(),
e.Callee.Func.RelString(pkg.Pkg))
got[edge] = true
return nil
})
compare("edge", got, wantEdge)
}
// Check reachable functions.
{
got := make(stringset)
for f := range res.Reachable {
got[f.RelString(pkg.Pkg)] = true
}
compare("reachable", got, wantReachable)
}
// Check runtime types.
{
got := make(stringset)
res.RuntimeTypes.Iterate(func(key types.Type, value any) {
if !value.(bool) { // accessible to reflection
typ := types.TypeString(types.Unalias(key), types.RelativeTo(pkg.Pkg))
got[typ] = true
}
})
compare("rtype", got, wantRtype)
}
}
================================================
FILE: go/callgraph/rta/testdata/func.txtar
================================================
-- go.mod --
module example.com
go 1.18
-- func.go --
package main
// Test of dynamic function calls.
// No interfaces, so no runtime/reflect types.
func A1() {
A2(0)
}
func A2(int) {} // not address-taken
func B() {} // unreachable
var (
C = func(int) {}
D = func(int) {}
)
func main() {
A1()
pfn := C
pfn(0) // calls C and D but not A2 (same sig but not address-taken)
}
// WANT:
//
// edge main --dynamic function call--> init$1
// edge main --dynamic function call--> init$2
//
// reachable A1
// reachable A2
// reachable init$1
// reachable init$2
// !reachable B
// reachable main
================================================
FILE: go/callgraph/rta/testdata/generics.txtar
================================================
-- go.mod --
module example.com
go 1.18
-- generics.go --
package main
// Test of generic function calls.
type I interface {
Foo()
}
type A struct{}
func (a A) Foo() {}
type B struct{}
func (b B) Foo() {}
func instantiated[X I](x X) {
x.Foo()
}
var a A
var b B
func main() {
instantiated[A](a) // static call
instantiated[B](b) // static call
local[C]().Foo()
lambda[A]()()()
}
func local[X I]() I {
var x X
return x
}
type C struct{}
func (c C) Foo() {}
func lambda[X I]() func() func() {
return func() func() {
var x X
return x.Foo
}
}
// WANT:
//
// edge (*C).Foo --static method call--> (C).Foo
// edge (A).Foo$bound --static method call--> (A).Foo
// edge instantiated[example.com.A] --static method call--> (A).Foo
// edge instantiated[example.com.B] --static method call--> (B).Foo
// edge main --dynamic method call--> (*C).Foo
// edge main --dynamic function call--> (A).Foo$bound
// edge main --dynamic method call--> (C).Foo
// edge main --static function call--> instantiated[example.com.A]
// edge main --static function call--> instantiated[example.com.B]
// edge main --static function call--> lambda[example.com.A]
// edge main --dynamic function call--> lambda[example.com.A]$1
// edge main --static function call--> local[example.com.C]
//
// reachable (*C).Foo
// reachable (A).Foo
// reachable (A).Foo$bound
// reachable (B).Foo
// reachable (C).Foo
// reachable instantiated[example.com.A]
// reachable instantiated[example.com.B]
// reachable lambda[example.com.A]
// reachable lambda[example.com.A]$1
// reachable local[example.com.C]
//
// rtype *C
// rtype C
================================================
FILE: go/callgraph/rta/testdata/iface.txtar
================================================
-- go.mod --
module example.com
go 1.18
-- iface.go --
package main
// Test of interface calls.
func use(interface{})
type A byte // instantiated but not a reflect type
func (A) f() {} // called directly
func (A) F() {} // unreachable
type B int // a reflect type
func (*B) f() {} // reachable via interface invoke
func (*B) F() {} // reachable: exported method of reflect type
type B2 int // a reflect type, and *B2 also
func (B2) f() {} // reachable via interface invoke
func (B2) g() {} // reachable: exported method of reflect type
type C string // not instantiated
func (C) f() {} // unreachable
func (C) F() {} // unreachable
type D uint // instantiated only in dead code
func (D) f() {} // unreachable
func (D) F() {} // unreachable
func main() {
A(0).f()
use(new(B))
use(B2(0))
var i interface {
f()
}
i.f() // calls (*B).f, (*B2).f and (B2.f)
live()
}
func live() {
var j interface {
f()
g()
}
j.f() // calls (B2).f and (*B2).f but not (*B).f (no g method).
}
func dead() {
use(D(0))
}
// WANT:
//
// edge live --dynamic method call--> (*B2).f
// edge live --dynamic method call--> (B2).f
// edge main --dynamic method call--> (*B).f
// edge main --dynamic method call--> (*B2).f
// edge main --dynamic method call--> (B2).f
//
// reachable (A).f
// !reachable (A).F
// reachable (*B).f
// reachable (*B).F
// reachable (B2).f
// !reachable (B2).g
// reachable (*B2).f
// !reachable (*B2).g
// !reachable (C).f
// !reachable (C).F
// !reachable (D).f
// !reachable (D).F
// reachable main
// reachable live
// reachable use
// !reachable dead
//
// !rtype A
// rtype *B
// rtype *B2
// rtype B
// rtype B2
// !rtype C
// !rtype D
================================================
FILE: go/callgraph/rta/testdata/multipkgs.txtar
================================================
-- go.mod --
module example.com
go 1.18
-- iface.go --
package main
import (
"example.com/subpkg"
)
func use(interface{})
// Test of interface calls.
func main() {
use(subpkg.A(0))
use(new(subpkg.B))
use(subpkg.B2(0))
var i interface {
F()
}
// assign an interface type with a function return interface value
i = subpkg.NewInterfaceF()
i.F()
}
func dead() {
use(subpkg.D(0))
}
// WANT:
//
// edge (*example.com/subpkg.A).F --static method call--> (example.com/subpkg.A).F
// edge (*example.com/subpkg.B2).F --static method call--> (example.com/subpkg.B2).F
// edge (*example.com/subpkg.C).F --static method call--> (example.com/subpkg.C).F
// edge init --static function call--> example.com/subpkg.init
// edge main --dynamic method call--> (*example.com/subpkg.A).F
// edge main --dynamic method call--> (*example.com/subpkg.B).F
// edge main --dynamic method call--> (*example.com/subpkg.B2).F
// edge main --dynamic method call--> (*example.com/subpkg.C).F
// edge main --dynamic method call--> (example.com/subpkg.A).F
// edge main --dynamic method call--> (example.com/subpkg.B2).F
// edge main --dynamic method call--> (example.com/subpkg.C).F
// edge main --static function call--> example.com/subpkg.NewInterfaceF
// edge main --static function call--> use
//
// reachable (*example.com/subpkg.A).F
// reachable (*example.com/subpkg.B).F
// reachable (*example.com/subpkg.B2).F
// reachable (*example.com/subpkg.C).F
// reachable (example.com/subpkg.A).F
// !reachable (example.com/subpkg.B).F
// reachable (example.com/subpkg.B2).F
// reachable (example.com/subpkg.C).F
// reachable example.com/subpkg.NewInterfaceF
// reachable example.com/subpkg.init
// !reachable (*example.com/subpkg.D).F
// !reachable (example.com/subpkg.D).F
// reachable init
// reachable main
// reachable use
//
// rtype *example.com/subpkg.A
// rtype *example.com/subpkg.B
// rtype *example.com/subpkg.B2
// rtype *example.com/subpkg.C
// rtype example.com/subpkg.B
// rtype example.com/subpkg.A
// rtype example.com/subpkg.B2
// rtype example.com/subpkg.C
// !rtype example.com/subpkg.D
-- subpkg/impl.go --
package subpkg
type InterfaceF interface {
F()
}
type A byte // instantiated but not a reflect type
func (A) F() {} // reachable: exported method of reflect type
type B int // a reflect type
func (*B) F() {} // reachable: exported method of reflect type
type B2 int // a reflect type, and *B2 also
func (B2) F() {} // reachable: exported method of reflect type
type C string
func (C) F() {} // reachable: exported by NewInterfaceF
func NewInterfaceF() InterfaceF {
return C("")
}
type D uint // instantiated only in dead code
func (*D) F() {} // unreachable
================================================
FILE: go/callgraph/rta/testdata/reflectcall.txtar
================================================
-- go.mod --
module example.com
go 1.18
-- reflectcall.go --
// Test of a reflective call to an address-taken function.
//
// Dynamically, this program executes both print statements.
// RTA should report the hello methods as reachable,
// even though there are no dynamic calls of type func(U)
// and the type T is not live.
package main
import "reflect"
type T int
type U int // to ensure the hello methods' signatures are unique
func (T) hello(U) { println("hello") }
type T2 int
func (T2) Hello(U, U) { println("T2.Hello") }
func main() {
u := reflect.ValueOf(U(0))
// reflective call to bound method closure T.hello
reflect.ValueOf(T(0).hello).Call([]reflect.Value{u})
// reflective call to exported method "Hello" of rtype T2.
reflect.ValueOf(T2(0)).Method(0).Call([]reflect.Value{u, u})
}
// WANT:
//
// edge (reflect.Value).Call --synthetic call--> (T).hello$bound
// edge (T).hello$bound --static method call--> (T).hello
// edge main --static function call--> reflect.ValueOf
// edge main --static method call--> (reflect.Value).Call
// edge (*T2).Hello --static method call--> (T2).Hello
//
// reachable (T).hello
// reachable (T).hello$bound
// reachable (T2).Hello
//
// !rtype T
// rtype T2
// rtype U
================================================
FILE: go/callgraph/rta/testdata/rtype.txtar
================================================
-- go.mod --
module example.com
go 1.18
-- rtype.go --
package main
// Test of runtime types (types for which descriptors are needed).
func use(interface{})
type A byte // neither A nor byte are runtime types
type B struct{ x uint } // B and uint are runtime types, but not the struct
func main() {
var x int // not a runtime type
print(x)
var y string // runtime type due to interface conversion
use(y)
use(struct{ uint64 }{}) // struct is a runtime type
use(new(B)) // *B is a runtime type
}
// WANT:
//
// reachable main
// reachable use
//
// !rtype A
// !rtype struct{uint}
// rtype *B
// rtype B
// rtype string
// rtype struct{uint64}
// rtype uint
// rtype uint64
// !rtype int
================================================
FILE: go/callgraph/static/static.go
================================================
// Copyright 2014 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package static computes the call graph of a Go program containing
// only static call edges.
package static
import (
"go/types"
"golang.org/x/tools/go/callgraph"
"golang.org/x/tools/go/ssa"
)
// CallGraph computes the static call graph of the specified program.
//
// The resulting graph includes:
// - all package-level functions;
// - all methods of package-level non-parameterized non-interface types;
// - pointer wrappers (*C).F for source-level methods C.F;
// - and all functions reachable from them following only static calls.
//
// It does not consider exportedness, nor treat main packages specially.
func CallGraph(prog *ssa.Program) *callgraph.Graph {
cg := callgraph.New(nil)
// Recursively follow all static calls.
seen := make(map[int]bool) // node IDs already seen
var visit func(fnode *callgraph.Node)
visit = func(fnode *callgraph.Node) {
if !seen[fnode.ID] {
seen[fnode.ID] = true
for _, b := range fnode.Func.Blocks {
for _, instr := range b.Instrs {
if site, ok := instr.(ssa.CallInstruction); ok {
if g := site.Common().StaticCallee(); g != nil {
gnode := cg.CreateNode(g)
callgraph.AddEdge(fnode, site, gnode)
visit(gnode)
}
}
}
}
}
}
// If we were ever to redesign this function, we should allow
// the caller to provide the set of root functions and just
// perform the reachability step. This would allow them to
// work forwards from main entry points:
//
// rootNames := []string{"init", "main"}
// for _, main := range ssautil.MainPackages(prog.AllPackages()) {
// for _, rootName := range rootNames {
// visit(cg.CreateNode(main.Func(rootName)))
// }
// }
//
// or to control whether to include non-exported
// functions/methods, wrapper methods, and so on.
// Unfortunately that's not consistent with its historical
// behavior and existing tests.
//
// The logic below is a slight simplification and
// rationalization of ssautil.AllFunctions. (Having to include
// (*T).F wrapper methods is unfortunate--they are not source
// functions, and if they're reachable, they'll be in the
// graph--but the existing tests will break without it.)
methodsOf := func(T types.Type) {
if !types.IsInterface(T) {
mset := prog.MethodSets.MethodSet(T)
for method := range mset.Methods() {
visit(cg.CreateNode(prog.MethodValue(method)))
}
}
}
// Start from package-level symbols.
for _, pkg := range prog.AllPackages() {
for _, mem := range pkg.Members {
switch mem := mem.(type) {
case *ssa.Function:
// package-level function
visit(cg.CreateNode(mem))
case *ssa.Type:
// methods of package-level non-interface non-parameterized types
if !types.IsInterface(mem.Type()) {
if named, ok := mem.Type().(*types.Named); ok &&
named.TypeParams() == nil {
methodsOf(named) // T
methodsOf(types.NewPointer(named)) // *T
}
}
}
}
}
return cg
}
================================================
FILE: go/callgraph/static/static_test.go
================================================
// Copyright 2014 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package static_test
import (
"fmt"
"reflect"
"sort"
"testing"
"golang.org/x/tools/go/callgraph"
"golang.org/x/tools/go/callgraph/static"
"golang.org/x/tools/go/ssa"
"golang.org/x/tools/go/ssa/ssautil"
"golang.org/x/tools/internal/testfiles"
"golang.org/x/tools/txtar"
)
const input = `
-- go.mod --
module x.io
go 1.22
-- p/p.go --
package main
type C int
func (C) f()
type I interface{f()}
func f() {
p := func() {}
g()
p() // SSA constant propagation => static
if unknown {
p = h
}
p() // dynamic
C(0).f()
}
func g() {
var i I = C(0)
i.f()
}
func h()
var unknown bool
func main() {
}
`
const genericsInput = `
-- go.mod --
module x.io
go 1.22
-- p/p.go --
package p
type I interface {
F()
}
type A struct{}
func (a A) F() {}
type B struct{}
func (b B) F() {}
func instantiated[X I](x X) {
x.F()
}
func Bar() {}
func f(h func(), a A, b B) {
h()
instantiated[A](a)
instantiated[B](b)
}
`
func TestStatic(t *testing.T) {
for _, e := range []struct {
input string
want []string
}{
{input, []string{
"(*C).f -> (C).f",
"f -> (C).f",
"f -> f$1",
"f -> g",
}},
{genericsInput, []string{
"(*A).F -> (A).F",
"(*B).F -> (B).F",
"f -> instantiated[x.io/p.A]",
"f -> instantiated[x.io/p.B]",
"instantiated[x.io/p.A] -> (A).F",
"instantiated[x.io/p.B] -> (B).F",
}},
} {
pkgs := testfiles.LoadPackages(t, txtar.Parse([]byte(e.input)), "./p")
prog, _ := ssautil.Packages(pkgs, ssa.InstantiateGenerics)
prog.Build()
p := pkgs[0].Types
cg := static.CallGraph(prog)
var edges []string
callgraph.GraphVisitEdges(cg, func(e *callgraph.Edge) error {
edges = append(edges, fmt.Sprintf("%s -> %s",
e.Caller.Func.RelString(p),
e.Callee.Func.RelString(p)))
return nil
})
sort.Strings(edges)
if !reflect.DeepEqual(edges, e.want) {
t.Errorf("Got edges %v, want %v", edges, e.want)
}
}
}
================================================
FILE: go/callgraph/util.go
================================================
// Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package callgraph
import "golang.org/x/tools/go/ssa"
// This file provides various utilities over call graphs, such as
// visitation and path search.
// CalleesOf returns a new set containing all direct callees of the
// caller node.
func CalleesOf(caller *Node) map[*Node]bool {
callees := make(map[*Node]bool)
for _, e := range caller.Out {
callees[e.Callee] = true
}
return callees
}
// GraphVisitEdges visits all the edges in graph g in depth-first order.
// The edge function is called for each edge in postorder. If it
// returns non-nil, visitation stops and GraphVisitEdges returns that
// value.
func GraphVisitEdges(g *Graph, edge func(*Edge) error) error {
seen := make(map[*Node]bool)
var visit func(n *Node) error
visit = func(n *Node) error {
if !seen[n] {
seen[n] = true
for _, e := range n.Out {
if err := visit(e.Callee); err != nil {
return err
}
if err := edge(e); err != nil {
return err
}
}
}
return nil
}
for _, n := range g.Nodes {
if err := visit(n); err != nil {
return err
}
}
return nil
}
// PathSearch finds an arbitrary path starting at node start and
// ending at some node for which isEnd() returns true. On success,
// PathSearch returns the path as an ordered list of edges; on
// failure, it returns nil.
func PathSearch(start *Node, isEnd func(*Node) bool) []*Edge {
stack := make([]*Edge, 0, 32)
seen := make(map[*Node]bool)
var search func(n *Node) []*Edge
search = func(n *Node) []*Edge {
if !seen[n] {
seen[n] = true
if isEnd(n) {
return stack
}
for _, e := range n.Out {
stack = append(stack, e) // push
if found := search(e.Callee); found != nil {
return found
}
stack = stack[:len(stack)-1] // pop
}
}
return nil
}
return search(start)
}
// DeleteSyntheticNodes removes from call graph g all nodes for
// functions that do not correspond to source syntax. For historical
// reasons, nodes for g.Root and package initializers are always
// kept.
//
// As nodes are removed, edges are created to preserve the
// reachability relation of the remaining nodes.
func (g *Graph) DeleteSyntheticNodes() {
// Measurements on the standard library and go.tools show that
// resulting graph has ~15% fewer nodes and 4-8% fewer edges
// than the input.
//
// Inlining a wrapper of in-degree m, out-degree n adds m*n
// and removes m+n edges. Since most wrappers are monomorphic
// (n=1) this results in a slight reduction. Polymorphic
// wrappers (n>1), e.g. from embedding an interface value
// inside a struct to satisfy some interface, cause an
// increase in the graph, but they seem to be uncommon.
// Hash all existing edges to avoid creating duplicates.
edges := make(map[Edge]bool)
for _, cgn := range g.Nodes {
for _, e := range cgn.Out {
edges[*e] = true
}
}
for fn, cgn := range g.Nodes {
if cgn == g.Root || isInit(cgn.Func) || fn.Syntax() != nil {
continue // keep
}
for _, eIn := range cgn.In {
for _, eOut := range cgn.Out {
newEdge := Edge{eIn.Caller, eIn.Site, eOut.Callee}
if edges[newEdge] {
continue // don't add duplicate
}
AddEdge(eIn.Caller, eIn.Site, eOut.Callee)
edges[newEdge] = true
}
}
g.DeleteNode(cgn)
}
}
func isInit(fn *ssa.Function) bool {
return fn.Pkg != nil && fn.Pkg.Func("init") == fn
}
// DeleteNode removes node n and its edges from the graph g.
// (NB: not efficient for batch deletion.)
func (g *Graph) DeleteNode(n *Node) {
n.deleteIns()
n.deleteOuts()
delete(g.Nodes, n.Func)
}
// deleteIns deletes all incoming edges to n.
func (n *Node) deleteIns() {
for _, e := range n.In {
removeOutEdge(e)
}
n.In = nil
}
// deleteOuts deletes all outgoing edges from n.
func (n *Node) deleteOuts() {
for _, e := range n.Out {
removeInEdge(e)
}
n.Out = nil
}
// removeOutEdge removes edge.Caller's outgoing edge 'edge'.
func removeOutEdge(edge *Edge) {
caller := edge.Caller
n := len(caller.Out)
for i, e := range caller.Out {
if e == edge {
// Replace it with the final element and shrink the slice.
caller.Out[i] = caller.Out[n-1]
caller.Out[n-1] = nil // aid GC
caller.Out = caller.Out[:n-1]
return
}
}
panic("edge not found: " + edge.String())
}
// removeInEdge removes edge.Callee's incoming edge 'edge'.
func removeInEdge(edge *Edge) {
caller := edge.Callee
n := len(caller.In)
for i, e := range caller.In {
if e == edge {
// Replace it with the final element and shrink the slice.
caller.In[i] = caller.In[n-1]
caller.In[n-1] = nil // aid GC
caller.In = caller.In[:n-1]
return
}
}
panic("edge not found: " + edge.String())
}
================================================
FILE: go/callgraph/vta/graph.go
================================================
// Copyright 2021 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package vta
import (
"fmt"
"go/token"
"go/types"
"iter"
"golang.org/x/tools/go/ssa"
"golang.org/x/tools/go/types/typeutil"
"golang.org/x/tools/internal/typeparams"
)
// node interface for VTA nodes.
type node interface {
Type() types.Type
String() string
}
// constant node for VTA.
type constant struct {
typ types.Type
}
func (c constant) Type() types.Type {
return c.typ
}
func (c constant) String() string {
return fmt.Sprintf("Constant(%v)", c.Type())
}
// pointer node for VTA.
type pointer struct {
typ *types.Pointer
}
func (p pointer) Type() types.Type {
return p.typ
}
func (p pointer) String() string {
return fmt.Sprintf("Pointer(%v)", p.Type())
}
// mapKey node for VTA, modeling reachable map key types.
type mapKey struct {
typ types.Type
}
func (mk mapKey) Type() types.Type {
return mk.typ
}
func (mk mapKey) String() string {
return fmt.Sprintf("MapKey(%v)", mk.Type())
}
// mapValue node for VTA, modeling reachable map value types.
type mapValue struct {
typ types.Type
}
func (mv mapValue) Type() types.Type {
return mv.typ
}
func (mv mapValue) String() string {
return fmt.Sprintf("MapValue(%v)", mv.Type())
}
// sliceElem node for VTA, modeling reachable slice and array element types.
type sliceElem struct {
typ types.Type
}
func (s sliceElem) Type() types.Type {
return s.typ
}
func (s sliceElem) String() string {
return fmt.Sprintf("Slice([]%v)", s.Type())
}
// channelElem node for VTA, modeling reachable channel element types.
type channelElem struct {
typ types.Type
}
func (c channelElem) Type() types.Type {
return c.typ
}
func (c channelElem) String() string {
return fmt.Sprintf("Channel(chan %v)", c.Type())
}
// field node for VTA.
type field struct {
StructType types.Type
index int // index of the field in the struct
}
func (f field) Type() types.Type {
s := typeparams.CoreType(f.StructType).(*types.Struct)
return s.Field(f.index).Type()
}
func (f field) String() string {
s := typeparams.CoreType(f.StructType).(*types.Struct)
return fmt.Sprintf("Field(%v:%s)", f.StructType, s.Field(f.index).Name())
}
// global node for VTA.
type global struct {
val *ssa.Global
}
func (g global) Type() types.Type {
return g.val.Type()
}
func (g global) String() string {
return fmt.Sprintf("Global(%s)", g.val.Name())
}
// local node for VTA modeling local variables
// and function/method parameters.
type local struct {
val ssa.Value
}
func (l local) Type() types.Type {
return l.val.Type()
}
func (l local) String() string {
return fmt.Sprintf("Local(%s)", l.val.Name())
}
// indexedLocal node for VTA node. Models indexed locals
// related to the ssa extract instructions.
type indexedLocal struct {
val ssa.Value
index int
typ types.Type
}
func (i indexedLocal) Type() types.Type {
return i.typ
}
func (i indexedLocal) String() string {
return fmt.Sprintf("Local(%s[%d])", i.val.Name(), i.index)
}
// function node for VTA.
type function struct {
f *ssa.Function
}
func (f function) Type() types.Type {
return f.f.Type()
}
func (f function) String() string {
return fmt.Sprintf("Function(%s)", f.f.Name())
}
// resultVar represents the result
// variable of a function, whether
// named or not.
type resultVar struct {
f *ssa.Function
index int // valid index into result var tuple
}
func (o resultVar) Type() types.Type {
return o.f.Signature.Results().At(o.index).Type()
}
func (o resultVar) String() string {
v := o.f.Signature.Results().At(o.index)
if n := v.Name(); n != "" {
return fmt.Sprintf("Return(%s[%s])", o.f.Name(), n)
}
return fmt.Sprintf("Return(%s[%d])", o.f.Name(), o.index)
}
// nestedPtrInterface node represents all references and dereferences
// of locals and globals that have a nested pointer to interface type.
// We merge such constructs into a single node for simplicity and without
// much precision sacrifice as such variables are rare in practice. Both
// a and b would be represented as the same PtrInterface(I) node in:
//
// type I interface
// var a ***I
// var b **I
type nestedPtrInterface struct {
typ types.Type
}
func (l nestedPtrInterface) Type() types.Type {
return l.typ
}
func (l nestedPtrInterface) String() string {
return fmt.Sprintf("PtrInterface(%v)", l.typ)
}
// nestedPtrFunction node represents all references and dereferences of locals
// and globals that have a nested pointer to function type. We merge such
// constructs into a single node for simplicity and without much precision
// sacrifice as such variables are rare in practice. Both a and b would be
// represented as the same PtrFunction(func()) node in:
//
// var a *func()
// var b **func()
type nestedPtrFunction struct {
typ types.Type
}
func (p nestedPtrFunction) Type() types.Type {
return p.typ
}
func (p nestedPtrFunction) String() string {
return fmt.Sprintf("PtrFunction(%v)", p.typ)
}
// panicArg models types of all arguments passed to panic.
type panicArg struct{}
func (p panicArg) Type() types.Type {
return nil
}
func (p panicArg) String() string {
return "Panic"
}
// recoverReturn models types of all return values of recover().
type recoverReturn struct{}
func (r recoverReturn) Type() types.Type {
return nil
}
func (r recoverReturn) String() string {
return "Recover"
}
type empty = struct{}
// idx is an index representing a unique node in a vtaGraph.
type idx int
// vtaGraph remembers for each VTA node the set of its successors.
// Tailored for VTA, hence does not support singleton (sub)graphs.
type vtaGraph struct {
m []map[idx]empty // m[i] has the successors for the node with index i.
idx map[node]idx // idx[n] is the index for the node n.
node []node // node[i] is the node with index i.
}
func (g *vtaGraph) numNodes() int {
return len(g.idx)
}
func (g *vtaGraph) successors(x idx) iter.Seq[idx] {
return func(yield func(y idx) bool) {
for y := range g.m[x] {
if !yield(y) {
return
}
}
}
}
// addEdge adds an edge x->y to the graph.
func (g *vtaGraph) addEdge(x, y node) {
if g.idx == nil {
g.idx = make(map[node]idx)
}
lookup := func(n node) idx {
i, ok := g.idx[n]
if !ok {
i = idx(len(g.idx))
g.m = append(g.m, nil)
g.idx[n] = i
g.node = append(g.node, n)
}
return i
}
a := lookup(x)
b := lookup(y)
succs := g.m[a]
if succs == nil {
succs = make(map[idx]empty)
g.m[a] = succs
}
succs[b] = empty{}
}
// typePropGraph builds a VTA graph for a set of `funcs` and initial
// `callgraph` needed to establish interprocedural edges. Returns the
// graph and a map for unique type representatives.
func typePropGraph(funcs map[*ssa.Function]bool, callees calleesFunc) (*vtaGraph, *typeutil.Map) {
b := builder{callees: callees}
b.visit(funcs)
b.callees = nil // ensure callees is not pinned by pointers to other fields of b.
return &b.graph, &b.canon
}
// Data structure responsible for linearly traversing the
// code and building a VTA graph.
type builder struct {
graph vtaGraph
callees calleesFunc // initial call graph for creating flows at unresolved call sites.
// Specialized type map for canonicalization of types.Type.
// Semantically equivalent types can have different implementations,
// i.e., they are different pointer values. The map allows us to
// have one unique representative. The keys are fixed and from the
// client perspective they are types. The values in our case are
// types too, in particular type representatives. Each value is a
// pointer so this map is not expected to take much memory.
canon typeutil.Map
}
func (b *builder) visit(funcs map[*ssa.Function]bool) {
// Add the fixed edge Panic -> Recover
b.graph.addEdge(panicArg{}, recoverReturn{})
for f, in := range funcs {
if in {
b.fun(f)
}
}
}
func (b *builder) fun(f *ssa.Function) {
for _, bl := range f.Blocks {
for _, instr := range bl.Instrs {
b.instr(instr)
}
}
}
func (b *builder) instr(instr ssa.Instruction) {
switch i := instr.(type) {
case *ssa.Store:
b.addInFlowAliasEdges(b.nodeFromVal(i.Addr), b.nodeFromVal(i.Val))
case *ssa.MakeInterface:
b.addInFlowEdge(b.nodeFromVal(i.X), b.nodeFromVal(i))
case *ssa.MakeClosure:
b.closure(i)
case *ssa.UnOp:
b.unop(i)
case *ssa.Phi:
b.phi(i)
case *ssa.ChangeInterface:
// Although in change interface a := A(b) command a and b are
// the same object, the only interesting flow happens when A
// is an interface. We create flow b -> a, but omit a -> b.
// The latter flow is not needed: if a gets assigned concrete
// type later on, that cannot be propagated back to b as b
// is a separate variable. The a -> b flow can happen when
// A is a pointer to interface, but then the command is of
// type ChangeType, handled below.
b.addInFlowEdge(b.nodeFromVal(i.X), b.nodeFromVal(i))
case *ssa.ChangeType:
// change type command a := A(b) results in a and b being the
// same value. For concrete type A, there is no interesting flow.
//
// When A is an interface, most interface casts are handled
// by the ChangeInterface instruction. The relevant case here is
// when converting a pointer to an interface type. This can happen
// when the underlying interfaces have the same method set.
//
// type I interface{ foo() }
// type J interface{ foo() }
// var b *I
// a := (*J)(b)
//
// When this happens we add flows between a <--> b.
b.addInFlowAliasEdges(b.nodeFromVal(i), b.nodeFromVal(i.X))
case *ssa.TypeAssert:
b.tassert(i)
case *ssa.Extract:
b.extract(i)
case *ssa.Field:
b.field(i)
case *ssa.FieldAddr:
b.fieldAddr(i)
case *ssa.Send:
b.send(i)
case *ssa.Select:
b.selekt(i)
case *ssa.Index:
b.index(i)
case *ssa.IndexAddr:
b.indexAddr(i)
case *ssa.Lookup:
b.lookup(i)
case *ssa.MapUpdate:
b.mapUpdate(i)
case *ssa.Next:
b.next(i)
case ssa.CallInstruction:
b.call(i)
case *ssa.Panic:
b.panic(i)
case *ssa.Return:
b.rtrn(i)
case *ssa.MakeChan, *ssa.MakeMap, *ssa.MakeSlice, *ssa.BinOp,
*ssa.Alloc, *ssa.DebugRef, *ssa.Convert, *ssa.Jump, *ssa.If,
*ssa.Slice, *ssa.SliceToArrayPointer, *ssa.Range, *ssa.RunDefers:
// No interesting flow here.
// Notes on individual instructions:
// SliceToArrayPointer: t1 = slice to array pointer *[4]T <- []T (t0)
// No interesting flow as sliceArrayElem(t1) == sliceArrayElem(t0).
return
case *ssa.MultiConvert:
b.multiconvert(i)
default:
panic(fmt.Sprintf("unsupported instruction %v\n", instr))
}
}
func (b *builder) unop(u *ssa.UnOp) {
switch u.Op {
case token.MUL:
// Multiplication operator * is used here as a dereference operator.
b.addInFlowAliasEdges(b.nodeFromVal(u), b.nodeFromVal(u.X))
case token.ARROW:
t := typeparams.CoreType(u.X.Type()).(*types.Chan).Elem()
b.addInFlowAliasEdges(b.nodeFromVal(u), channelElem{typ: t})
default:
// There is no interesting type flow otherwise.
}
}
func (b *builder) phi(p *ssa.Phi) {
for _, edge := range p.Edges {
b.addInFlowAliasEdges(b.nodeFromVal(p), b.nodeFromVal(edge))
}
}
func (b *builder) tassert(a *ssa.TypeAssert) {
if !a.CommaOk {
b.addInFlowEdge(b.nodeFromVal(a.X), b.nodeFromVal(a))
return
}
// The case where a is register so there
// is a flow from a.X to a[0]. Here, a[0] is represented as an
// indexedLocal: an entry into local tuple register a at index 0.
tup := a.Type().(*types.Tuple)
t := tup.At(0).Type()
local := indexedLocal{val: a, typ: t, index: 0}
b.addInFlowEdge(b.nodeFromVal(a.X), local)
}
// extract instruction t1 := t2[i] generates flows between t2[i]
// and t1 where the source is indexed local representing a value
// from tuple register t2 at index i and the target is t1.
func (b *builder) extract(e *ssa.Extract) {
tup := e.Tuple.Type().(*types.Tuple)
t := tup.At(e.Index).Type()
local := indexedLocal{val: e.Tuple, typ: t, index: e.Index}
b.addInFlowAliasEdges(b.nodeFromVal(e), local)
}
func (b *builder) field(f *ssa.Field) {
fnode := field{StructType: f.X.Type(), index: f.Field}
b.addInFlowEdge(fnode, b.nodeFromVal(f))
}
func (b *builder) fieldAddr(f *ssa.FieldAddr) {
t := typeparams.CoreType(f.X.Type()).(*types.Pointer).Elem()
// Since we are getting pointer to a field, make a bidirectional edge.
fnode := field{StructType: t, index: f.Field}
b.addInFlowEdge(fnode, b.nodeFromVal(f))
b.addInFlowEdge(b.nodeFromVal(f), fnode)
}
func (b *builder) send(s *ssa.Send) {
t := typeparams.CoreType(s.Chan.Type()).(*types.Chan).Elem()
b.addInFlowAliasEdges(channelElem{typ: t}, b.nodeFromVal(s.X))
}
// selekt generates flows for select statement
//
// a = select blocking/nonblocking [c_1 <- t_1, c_2 <- t_2, ..., <- o_1, <- o_2, ...]
//
// between receiving channel registers c_i and corresponding input register t_i. Further,
// flows are generated between o_i and a[2 + i]. Note that a is a tuple register of type
// where the type of r_i is the element type of channel o_i.
func (b *builder) selekt(s *ssa.Select) {
recvIndex := 0
for _, state := range s.States {
t := typeparams.CoreType(state.Chan.Type()).(*types.Chan).Elem()
if state.Dir == types.SendOnly {
b.addInFlowAliasEdges(channelElem{typ: t}, b.nodeFromVal(state.Send))
} else {
// state.Dir == RecvOnly by definition of select instructions.
tupEntry := indexedLocal{val: s, typ: t, index: 2 + recvIndex}
b.addInFlowAliasEdges(tupEntry, channelElem{typ: t})
recvIndex++
}
}
}
// index instruction a := b[c] on slices creates flows between a and
// SliceElem(t) flow where t is an interface type of c. Arrays and
// slice elements are both modeled as SliceElem.
func (b *builder) index(i *ssa.Index) {
et := sliceArrayElem(i.X.Type())
b.addInFlowAliasEdges(b.nodeFromVal(i), sliceElem{typ: et})
}
// indexAddr instruction a := &b[c] fetches address of a index
// into the field so we create bidirectional flow a <-> SliceElem(t)
// where t is an interface type of c. Arrays and slice elements are
// both modeled as SliceElem.
func (b *builder) indexAddr(i *ssa.IndexAddr) {
et := sliceArrayElem(i.X.Type())
b.addInFlowEdge(sliceElem{typ: et}, b.nodeFromVal(i))
b.addInFlowEdge(b.nodeFromVal(i), sliceElem{typ: et})
}
// lookup handles map query commands a := m[b] where m is of type
// map[...]V and V is an interface. It creates flows between `a`
// and MapValue(V).
func (b *builder) lookup(l *ssa.Lookup) {
t, ok := l.X.Type().Underlying().(*types.Map)
if !ok {
// No interesting flows for string lookups.
return
}
if !l.CommaOk {
b.addInFlowAliasEdges(b.nodeFromVal(l), mapValue{typ: t.Elem()})
} else {
i := indexedLocal{val: l, typ: t.Elem(), index: 0}
b.addInFlowAliasEdges(i, mapValue{typ: t.Elem()})
}
}
// mapUpdate handles map update commands m[b] = a where m is of type
// map[K]V and K and V are interfaces. It creates flows between `a`
// and MapValue(V) as well as between MapKey(K) and `b`.
func (b *builder) mapUpdate(u *ssa.MapUpdate) {
t, ok := u.Map.Type().Underlying().(*types.Map)
if !ok {
// No interesting flows for string updates.
return
}
b.addInFlowAliasEdges(mapKey{typ: t.Key()}, b.nodeFromVal(u.Key))
b.addInFlowAliasEdges(mapValue{typ: t.Elem()}, b.nodeFromVal(u.Value))
}
// next instruction := next r, where r
// is a range over map or string generates flow between
// key and MapKey as well value and MapValue nodes.
func (b *builder) next(n *ssa.Next) {
if n.IsString {
return
}
tup := n.Type().(*types.Tuple)
kt := tup.At(1).Type()
vt := tup.At(2).Type()
b.addInFlowAliasEdges(indexedLocal{val: n, typ: kt, index: 1}, mapKey{typ: kt})
b.addInFlowAliasEdges(indexedLocal{val: n, typ: vt, index: 2}, mapValue{typ: vt})
}
// addInFlowAliasEdges adds an edge r -> l to b.graph if l is a node that can
// have an inflow, i.e., a node that represents an interface or an unresolved
// function value. Similarly for the edge l -> r with an additional condition
// of that l and r can potentially alias.
func (b *builder) addInFlowAliasEdges(l, r node) {
b.addInFlowEdge(r, l)
if canAlias(l, r) {
b.addInFlowEdge(l, r)
}
}
func (b *builder) closure(c *ssa.MakeClosure) {
f := c.Fn.(*ssa.Function)
b.addInFlowEdge(function{f: f}, b.nodeFromVal(c))
for i, fv := range f.FreeVars {
b.addInFlowAliasEdges(b.nodeFromVal(fv), b.nodeFromVal(c.Bindings[i]))
}
}
// panic creates a flow from arguments to panic instructions to return
// registers of all recover statements in the program. Introduces a
// global panic node Panic and
// 1. for every panic statement p: add p -> Panic
// 2. for every recover statement r: add Panic -> r (handled in call)
//
// TODO(zpavlinovic): improve precision by explicitly modeling how panic
// values flow from callees to callers and into deferred recover instructions.
func (b *builder) panic(p *ssa.Panic) {
// Panics often have, for instance, strings as arguments which do
// not create interesting flows.
if !canHaveMethods(p.X.Type()) {
return
}
b.addInFlowEdge(b.nodeFromVal(p.X), panicArg{})
}
// call adds flows between arguments/parameters and return values/registers
// for both static and dynamic calls, as well as go and defer calls.
func (b *builder) call(c ssa.CallInstruction) {
// When c is r := recover() call register instruction, we add Recover -> r.
if bf, ok := c.Common().Value.(*ssa.Builtin); ok && bf.Name() == "recover" {
if v, ok := c.(ssa.Value); ok {
b.addInFlowEdge(recoverReturn{}, b.nodeFromVal(v))
}
return
}
for f := range siteCallees(c, b.callees) {
addArgumentFlows(b, c, f)
site, ok := c.(ssa.Value)
if !ok {
continue // go or defer
}
results := f.Signature.Results()
if results.Len() == 1 {
// When there is only one return value, the destination register does not
// have a tuple type.
b.addInFlowEdge(resultVar{f: f, index: 0}, b.nodeFromVal(site))
} else {
tup := site.Type().(*types.Tuple)
for i := 0; i < results.Len(); i++ {
local := indexedLocal{val: site, typ: tup.At(i).Type(), index: i}
b.addInFlowEdge(resultVar{f: f, index: i}, local)
}
}
}
}
func addArgumentFlows(b *builder, c ssa.CallInstruction, f *ssa.Function) {
// When f has no parameters (including receiver), there is no type
// flow here. Also, f's body and parameters might be missing, such
// as when vta is used within the golang.org/x/tools/go/analysis
// framework (see github.com/golang/go/issues/50670).
if len(f.Params) == 0 {
return
}
cc := c.Common()
if cc.Method != nil {
// In principle we don't add interprocedural flows for receiver
// objects. At a call site, the receiver object is interface
// while the callee object is concrete. The flow from interface
// to concrete type in general does not make sense. The exception
// is when the concrete type is a named function type (see #57756).
//
// The flow other way around would bake in information from the
// initial call graph.
if isFunction(f.Params[0].Type()) {
b.addInFlowEdge(b.nodeFromVal(cc.Value), b.nodeFromVal(f.Params[0]))
}
}
offset := 0
if cc.Method != nil {
offset = 1
}
for i, v := range cc.Args {
// Parameters of f might not be available, as in the case
// when vta is used within the golang.org/x/tools/go/analysis
// framework (see github.com/golang/go/issues/50670).
//
// TODO: investigate other cases of missing body and parameters
if len(f.Params) <= i+offset {
return
}
b.addInFlowAliasEdges(b.nodeFromVal(f.Params[i+offset]), b.nodeFromVal(v))
}
}
// rtrn creates flow edges from the operands of the return
// statement to the result variables of the enclosing function.
func (b *builder) rtrn(r *ssa.Return) {
for i, rs := range r.Results {
b.addInFlowEdge(b.nodeFromVal(rs), resultVar{f: r.Parent(), index: i})
}
}
func (b *builder) multiconvert(c *ssa.MultiConvert) {
// TODO(zpavlinovic): decide what to do on MultiConvert long term.
// TODO(zpavlinovic): add unit tests.
typeSetOf := func(typ types.Type) []*types.Term {
// This is a adaptation of x/exp/typeparams.NormalTerms which x/tools cannot depend on.
var terms []*types.Term
var err error
switch typ := types.Unalias(typ).(type) {
case *types.TypeParam:
terms, err = typeparams.StructuralTerms(typ)
case *types.Union:
terms, err = typeparams.UnionTermSet(typ)
case *types.Interface:
terms, err = typeparams.InterfaceTermSet(typ)
default:
// Common case.
// Specializing the len=1 case to avoid a slice
// had no measurable space/time benefit.
terms = []*types.Term{types.NewTerm(false, typ)}
}
if err != nil {
return nil
}
return terms
}
// isValuePreserving returns true if a conversion from ut_src to
// ut_dst is value-preserving, i.e. just a change of type.
// Precondition: neither argument is a named or alias type.
isValuePreserving := func(ut_src, ut_dst types.Type) bool {
// Identical underlying types?
if types.IdenticalIgnoreTags(ut_dst, ut_src) {
return true
}
switch ut_dst.(type) {
case *types.Chan:
// Conversion between channel types?
_, ok := ut_src.(*types.Chan)
return ok
case *types.Pointer:
// Conversion between pointers with identical base types?
_, ok := ut_src.(*types.Pointer)
return ok
}
return false
}
dst_terms := typeSetOf(c.Type())
src_terms := typeSetOf(c.X.Type())
for _, s := range src_terms {
us := s.Type().Underlying()
for _, d := range dst_terms {
ud := d.Type().Underlying()
if isValuePreserving(us, ud) {
// This is equivalent to a ChangeType.
b.addInFlowAliasEdges(b.nodeFromVal(c), b.nodeFromVal(c.X))
return
}
// This is equivalent to either: SliceToArrayPointer,,
// SliceToArrayPointer+Deref, Size 0 Array constant, or a Convert.
}
}
}
// addInFlowEdge adds s -> d to g if d is node that can have an inflow, i.e., a node
// that represents an interface or an unresolved function value. Otherwise, there
// is no interesting type flow so the edge is omitted.
func (b *builder) addInFlowEdge(s, d node) {
if hasInFlow(d) {
b.graph.addEdge(b.representative(s), b.representative(d))
}
}
// Creates const, pointer, global, func, and local nodes based on register instructions.
func (b *builder) nodeFromVal(val ssa.Value) node {
if p, ok := types.Unalias(val.Type()).(*types.Pointer); ok && !types.IsInterface(p.Elem()) && !isFunction(p.Elem()) {
// Nested pointer to interfaces are modeled as a special
// nestedPtrInterface node.
if i := interfaceUnderPtr(p.Elem()); i != nil {
return nestedPtrInterface{typ: i}
}
// The same goes for nested function types.
if f := functionUnderPtr(p.Elem()); f != nil {
return nestedPtrFunction{typ: f}
}
return pointer{typ: p}
}
switch v := val.(type) {
case *ssa.Const:
return constant{typ: val.Type()}
case *ssa.Global:
return global{val: v}
case *ssa.Function:
return function{f: v}
case *ssa.Parameter, *ssa.FreeVar, ssa.Instruction:
// ssa.Param, ssa.FreeVar, and a specific set of "register" instructions,
// satisfying the ssa.Value interface, can serve as local variables.
return local{val: v}
default:
panic(fmt.Errorf("unsupported value %v in node creation", val))
}
}
// representative returns a unique representative for node `n`. Since
// semantically equivalent types can have different implementations,
// this method guarantees the same implementation is always used.
func (b *builder) representative(n node) node {
if n.Type() == nil {
// panicArg and recoverReturn do not have
// types and are unique by definition.
return n
}
t := canonicalize(n.Type(), &b.canon)
switch i := n.(type) {
case constant:
return constant{typ: t}
case pointer:
return pointer{typ: t.(*types.Pointer)}
case sliceElem:
return sliceElem{typ: t}
case mapKey:
return mapKey{typ: t}
case mapValue:
return mapValue{typ: t}
case channelElem:
return channelElem{typ: t}
case nestedPtrInterface:
return nestedPtrInterface{typ: t}
case nestedPtrFunction:
return nestedPtrFunction{typ: t}
case field:
return field{StructType: canonicalize(i.StructType, &b.canon), index: i.index}
case indexedLocal:
return indexedLocal{typ: t, val: i.val, index: i.index}
case local, global, panicArg, recoverReturn, function, resultVar:
return n
default:
panic(fmt.Errorf("canonicalizing unrecognized node %v", n))
}
}
// canonicalize returns a type representative of `t` unique subject
// to type map `canon`.
func canonicalize(t types.Type, canon *typeutil.Map) types.Type {
rep := canon.At(t)
if rep != nil {
return rep.(types.Type)
}
canon.Set(t, t)
return t
}
================================================
FILE: go/callgraph/vta/graph_test.go
================================================
// Copyright 2021 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package vta
import (
"fmt"
"go/types"
"reflect"
"sort"
"strings"
"testing"
"golang.org/x/tools/go/callgraph/cha"
"golang.org/x/tools/go/ssa"
"golang.org/x/tools/go/ssa/ssautil"
)
func TestNodeInterface(t *testing.T) {
// Since ssa package does not allow explicit creation of ssa
// values, we use the values from the program testdata/src/simple.go:
// - basic type int
// - struct X with two int fields a and b
// - global variable "gl"
// - "foo" function
// - "main" function and its
// - first register instruction t0 := *gl
prog, _, err := testProg(t, "testdata/src/simple.go", ssa.BuilderMode(0))
if err != nil {
t.Fatalf("couldn't load testdata/src/simple.go program: %v", err)
}
pkg := prog.AllPackages()[0]
main := pkg.Func("main")
foo := pkg.Func("foo")
reg := firstRegInstr(main) // t0 := *gl
X := pkg.Type("X").Type()
gl := pkg.Var("gl")
glPtrType, ok := types.Unalias(gl.Type()).(*types.Pointer)
if !ok {
t.Fatalf("could not cast gl variable to pointer type")
}
bint := glPtrType.Elem()
pint := types.NewPointer(bint)
i := types.NewInterface(nil, nil)
voidFunc := main.Signature.Underlying()
for _, test := range []struct {
n node
s string
t types.Type
}{
{constant{typ: bint}, "Constant(int)", bint},
{pointer{typ: pint}, "Pointer(*int)", pint},
{mapKey{typ: bint}, "MapKey(int)", bint},
{mapValue{typ: pint}, "MapValue(*int)", pint},
{sliceElem{typ: bint}, "Slice([]int)", bint},
{channelElem{typ: pint}, "Channel(chan *int)", pint},
{field{StructType: X, index: 0}, "Field(testdata.X:a)", bint},
{field{StructType: X, index: 1}, "Field(testdata.X:b)", bint},
{global{val: gl}, "Global(gl)", gl.Type()},
{local{val: reg}, "Local(t0)", bint},
{indexedLocal{val: reg, typ: X, index: 0}, "Local(t0[0])", X},
{function{f: main}, "Function(main)", voidFunc},
{resultVar{f: foo, index: 0}, "Return(foo[r])", bint},
{nestedPtrInterface{typ: i}, "PtrInterface(interface{})", i},
{nestedPtrFunction{typ: voidFunc}, "PtrFunction(func())", voidFunc},
{panicArg{}, "Panic", nil},
{recoverReturn{}, "Recover", nil},
} {
if removeModulePrefix(test.s) != removeModulePrefix(test.n.String()) {
t.Errorf("want %s; got %s", removeModulePrefix(test.s), removeModulePrefix(test.n.String()))
}
if test.t != test.n.Type() {
t.Errorf("want %s; got %s", test.t, test.n.Type())
}
}
}
// removeModulePrefix removes the "x.io/" module name prefix throughout s.
// (It is added by testProg.)
func removeModulePrefix(s string) string {
return strings.ReplaceAll(s, "x.io/", "")
}
func TestVtaGraph(t *testing.T) {
// Get the basic type int from a real program.
prog, _, err := testProg(t, "testdata/src/simple.go", ssa.BuilderMode(0))
if err != nil {
t.Fatalf("couldn't load testdata/src/simple.go program: %v", err)
}
glPtrType, ok := prog.AllPackages()[0].Var("gl").Type().(*types.Pointer)
if !ok {
t.Fatalf("could not cast gl variable to pointer type")
}
bint := glPtrType.Elem()
n1 := constant{typ: bint}
n2 := pointer{typ: types.NewPointer(bint)}
n3 := mapKey{typ: types.NewMap(bint, bint)}
n4 := mapValue{typ: types.NewMap(bint, bint)}
// Create graph
// n1 n2
// \ / /
// n3 /
// | /
// n4
var g vtaGraph
g.addEdge(n1, n3)
g.addEdge(n2, n3)
g.addEdge(n3, n4)
g.addEdge(n2, n4)
// for checking duplicates
g.addEdge(n1, n3)
want := vtaGraph{
m: []map[idx]empty{
map[idx]empty{1: empty{}},
map[idx]empty{3: empty{}},
map[idx]empty{1: empty{}, 3: empty{}},
nil,
},
idx: map[node]idx{
n1: 0,
n3: 1,
n2: 2,
n4: 3,
},
node: []node{n1, n3, n2, n4},
}
if !reflect.DeepEqual(want, g) {
t.Errorf("want %v; got %v", want, g)
}
for _, test := range []struct {
n node
l int
}{
{n1, 1},
{n2, 2},
{n3, 1},
{n4, 0},
} {
sl := 0
for range g.successors(g.idx[test.n]) {
sl++
}
if sl != test.l {
t.Errorf("want %d successors; got %d", test.l, sl)
}
}
}
// vtaGraphStr stringifies vtaGraph into a list of strings
// where each string represents an edge set of the format
// node -> succ_1, ..., succ_n. succ_1, ..., succ_n are
// sorted in alphabetical order.
func vtaGraphStr(g *vtaGraph) []string {
var vgs []string
for n := 0; n < g.numNodes(); n++ {
var succStr []string
for s := range g.successors(idx(n)) {
succStr = append(succStr, g.node[s].String())
}
sort.Strings(succStr)
entry := fmt.Sprintf("%v -> %v", g.node[n].String(), strings.Join(succStr, ", "))
vgs = append(vgs, removeModulePrefix(entry))
}
return vgs
}
// setdiff returns the set difference of `X-Y` or {s | s ∈ X, s ∉ Y }.
func setdiff(X, Y []string) []string {
y := make(map[string]bool)
var delta []string
for _, s := range Y {
y[s] = true
}
for _, s := range X {
if _, ok := y[s]; !ok {
delta = append(delta, s)
}
}
sort.Strings(delta)
return delta
}
func TestVTAGraphConstruction(t *testing.T) {
for _, file := range []string{
"testdata/src/store.go",
"testdata/src/phi.go",
"testdata/src/type_conversions.go",
"testdata/src/type_assertions.go",
"testdata/src/fields.go",
"testdata/src/node_uniqueness.go",
"testdata/src/store_load_alias.go",
"testdata/src/phi_alias.go",
"testdata/src/channels.go",
"testdata/src/generic_channels.go",
"testdata/src/select.go",
"testdata/src/stores_arrays.go",
"testdata/src/maps.go",
"testdata/src/ranges.go",
"testdata/src/closures.go",
"testdata/src/function_alias.go",
"testdata/src/static_calls.go",
"testdata/src/dynamic_calls.go",
"testdata/src/returns.go",
"testdata/src/panic.go",
} {
t.Run(file, func(t *testing.T) {
prog, want, err := testProg(t, file, ssa.BuilderMode(0))
if err != nil {
t.Fatalf("couldn't load test file '%s': %s", file, err)
}
if len(want) == 0 {
t.Fatalf("couldn't find want in `%s`", file)
}
fs := ssautil.AllFunctions(prog)
// First test propagation with lazy-CHA initial call graph.
g, _ := typePropGraph(fs, makeCalleesFunc(fs, nil))
got := vtaGraphStr(g)
if diff := setdiff(want, got); len(diff) > 0 {
t.Errorf("`%s`: want superset of %v;\n got %v\ndiff: %v", file, want, got, diff)
}
// Repeat the test with explicit CHA initial call graph.
g, _ = typePropGraph(fs, makeCalleesFunc(fs, cha.CallGraph(prog)))
got = vtaGraphStr(g)
if diff := setdiff(want, got); len(diff) > 0 {
t.Errorf("`%s`: want superset of %v;\n got %v\ndiff: %v", file, want, got, diff)
}
})
}
}
================================================
FILE: go/callgraph/vta/helpers_test.go
================================================
// Copyright 2021 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package vta
import (
"bytes"
"fmt"
"go/ast"
"os"
"path/filepath"
"sort"
"strings"
"testing"
"golang.org/x/tools/go/callgraph"
"golang.org/x/tools/go/packages"
"golang.org/x/tools/go/ssa/ssautil"
"golang.org/x/tools/internal/testenv"
"golang.org/x/tools/go/ssa"
)
// want extracts the contents of the first comment
// section starting with "WANT:\n". The returned
// content is split into lines without // prefix.
func want(f *ast.File) []string {
for _, c := range f.Comments {
text := strings.TrimSpace(c.Text())
if t, ok := strings.CutPrefix(text, "WANT:\n"); ok {
return strings.Split(t, "\n")
}
}
return nil
}
// testProg returns an ssa representation of a program at
// `path`, assumed to define package "testdata," and the
// test want result as list of strings.
func testProg(t testing.TB, path string, mode ssa.BuilderMode) (*ssa.Program, []string, error) {
// Set debug mode to exercise DebugRef instructions.
pkg, ssapkg := loadFile(t, path, mode|ssa.GlobalDebug)
return ssapkg.Prog, want(pkg.Syntax[0]), nil
}
// loadFile loads a built SSA package for a single-file package "x.io/testdata".
// (Ideally all uses would be converted over to txtar files with explicit go.mod files.)
//
// TODO(adonovan): factor with similar loadFile in cha/cha_test.go.
func loadFile(t testing.TB, filename string, mode ssa.BuilderMode) (*packages.Package, *ssa.Package) {
testenv.NeedsGoPackages(t)
data, err := os.ReadFile(filename)
if err != nil {
t.Fatal(err)
}
dir := t.TempDir()
cfg := &packages.Config{
Mode: packages.LoadAllSyntax,
Dir: dir,
Overlay: map[string][]byte{
filepath.Join(dir, "go.mod"): fmt.Appendf(nil, "module x.io\ngo 1.%d", testenv.Go1Point()),
filepath.Join(dir, "testdata", filepath.Base(filename)): data,
},
}
pkgs, err := packages.Load(cfg, "./testdata")
if err != nil {
t.Fatal(err)
}
if len(pkgs) != 1 {
t.Fatalf("got %d packages, want 1", len(pkgs))
}
if len(pkgs[0].Syntax) != 1 {
t.Fatalf("got %d files, want 1", len(pkgs[0].Syntax))
}
if num := packages.PrintErrors(pkgs); num > 0 {
t.Fatalf("packages contained %d errors", num)
}
prog, ssapkgs := ssautil.Packages(pkgs, mode)
prog.Build()
return pkgs[0], ssapkgs[0]
}
func firstRegInstr(f *ssa.Function) ssa.Value {
for _, b := range f.Blocks {
for _, i := range b.Instrs {
if v, ok := i.(ssa.Value); ok {
return v
}
}
}
return nil
}
// funcName returns a name of the function `f`
// prefixed with the name of the receiver type.
func funcName(f *ssa.Function) string {
recv := f.Signature.Recv()
if recv == nil {
return f.Name()
}
tp := recv.Type().String()
return tp[strings.LastIndex(tp, ".")+1:] + "." + f.Name()
}
// callGraphStr stringifes `g` into a list of strings where
// each entry is of the form
//
// f: cs1 -> f1, f2, ...; ...; csw -> fx, fy, ...
//
// f is a function, cs1, ..., csw are call sites in f, and
// f1, f2, ..., fx, fy, ... are the resolved callees.
func callGraphStr(g *callgraph.Graph) []string {
var gs []string
for f, n := range g.Nodes {
c := make(map[string][]string)
for _, edge := range n.Out {
cs := edge.Site.String() // TODO(adonovan): handle Site=nil gracefully
c[cs] = append(c[cs], funcName(edge.Callee.Func))
}
var cs []string
for site, fs := range c {
sort.Strings(fs)
entry := fmt.Sprintf("%v -> %v", site, strings.Join(fs, ", "))
cs = append(cs, entry)
}
sort.Strings(cs)
entry := fmt.Sprintf("%v: %v", funcName(f), strings.Join(cs, "; "))
gs = append(gs, removeModulePrefix(entry))
}
return gs
}
// Logs the functions of prog to t.
func logFns(t testing.TB, prog *ssa.Program) {
for fn := range ssautil.AllFunctions(prog) {
var buf bytes.Buffer
fn.WriteTo(&buf)
t.Log(buf.String())
}
}
================================================
FILE: go/callgraph/vta/initial.go
================================================
// Copyright 2024 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package vta
import (
"golang.org/x/tools/go/callgraph"
"golang.org/x/tools/go/callgraph/internal/chautil"
"golang.org/x/tools/go/ssa"
)
// calleesFunc abstracts call graph in one direction,
// from call sites to callees.
type calleesFunc func(ssa.CallInstruction) []*ssa.Function
// makeCalleesFunc returns an initial call graph for vta as a
// calleesFunc. If c is not nil, returns callees as given by c.
// Otherwise, it returns chautil.LazyCallees over fs.
func makeCalleesFunc(fs map[*ssa.Function]bool, c *callgraph.Graph) calleesFunc {
if c == nil {
return chautil.LazyCallees(fs)
}
return func(call ssa.CallInstruction) []*ssa.Function {
node := c.Nodes[call.Parent()]
if node == nil {
return nil
}
var cs []*ssa.Function
for _, edge := range node.Out {
if edge.Site == call {
cs = append(cs, edge.Callee.Func)
}
}
return cs
}
}
================================================
FILE: go/callgraph/vta/internal/trie/bits.go
================================================
// Copyright 2021 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package trie
import (
"math/bits"
)
// This file contains bit twiddling functions for Patricia tries.
// Consult this paper for details.
// C. Okasaki and A. Gill, “Fast mergeable integer maps,” in ACM SIGPLAN
// Workshop on ML, September 1998, pp. 77–86.
// key is a key in a Map.
type key uint64
// bitpos is the position of a bit. A position is represented by having a 1
// bit in that position.
// Examples:
// - 0b0010 is the position of the `1` bit in 2.
// It is the 3rd most specific bit position in big endian encoding
// (0b0 and 0b1 are more specific).
// - 0b0100 is the position of the bit that 1 and 5 disagree on.
// - 0b0 is a special value indicating that all bit agree.
type bitpos uint64
// prefixes represent a set of keys that all agree with the
// prefix up to a bitpos m.
//
// The value for a prefix is determined by the mask(k, m) function.
// (See mask for details on the values.)
// A `p` prefix for position `m` matches a key `k` iff mask(k, m) == p.
// A prefix always mask(p, m) == p.
//
// A key is its own prefix for the bit position 64,
// e.g. seeing a `prefix(key)` is not a problem.
//
// Prefixes should never be turned into keys.
type prefix uint64
// branchingBit returns the position of the first bit in `x` and `y`
// that are not equal.
func branchingBit(x, y prefix) bitpos {
p := x ^ y
if p == 0 {
return 0
}
return bitpos(1) << uint(bits.Len64(uint64(p))-1) // uint conversion needed for go1.12
}
// zeroBit returns true if k has a 0 bit at position `b`.
func zeroBit(k prefix, b bitpos) bool {
return (uint64(k) & uint64(b)) == 0
}
// matchPrefix returns true if a prefix k matches a prefix p up to position `b`.
func matchPrefix(k prefix, p prefix, b bitpos) bool {
return mask(k, b) == p
}
// mask returns a prefix of `k` with all bits after and including `b` zeroed out.
//
// In big endian encoding, this value is the [64-(m-1)] most significant bits of k
// followed by a `0` bit at bitpos m, followed m-1 `1` bits.
// Examples:
//
// prefix(0b1011) for a bitpos 0b0100 represents the keys:
// 0b1000, 0b1001, 0b1010, 0b1011, 0b1100, 0b1101, 0b1110, 0b1111
//
// This mask function has the property that if matchPrefix(k, p, b), then
// k <= p if and only if zeroBit(k, m). This induces binary search tree tries.
// See Okasaki & Gill for more details about this choice of mask function.
//
// mask is idempotent for a given `b`, i.e. mask(mask(p, b), b) == mask(p,b).
func mask(k prefix, b bitpos) prefix {
return prefix((uint64(k) | (uint64(b) - 1)) & (^uint64(b)))
}
// ord returns true if m comes before n in the bit ordering.
func ord(m, n bitpos) bool {
return m > n // big endian encoding
}
// prefixesOverlap returns true if there is some key a prefix `p` for bitpos `m`
// can hold that can also be held by a prefix `q` for some bitpos `n`.
//
// This is equivalent to:
//
// m ==n && p == q,
// higher(m, n) && matchPrefix(q, p, m), or
// higher(n, m) && matchPrefix(p, q, n)
func prefixesOverlap(p prefix, m bitpos, q prefix, n bitpos) bool {
fbb := n
if ord(m, n) {
fbb = m
}
return mask(p, fbb) == mask(q, fbb)
// Lemma:
// mask(p, fbb) == mask(q, fbb)
// iff
// m > n && matchPrefix(q, p, m) or (note: big endian encoding)
// m < n && matchPrefix(p, q, n) or (note: big endian encoding)
// m ==n && p == q
// Quick-n-dirty proof:
// p == mask(p0, m) for some p0 by precondition.
// q == mask(q0, n) for some q0 by precondition.
// So mask(p, m) == p and mask(q, n) == q as mask(*, n') is idempotent.
//
// [=> proof]
// Suppose mask(p, fbb) == mask(q, fbb).
// if m ==n, p == mask(p, m) == mask(p, fbb) == mask(q, fbb) == mask(q, n) == q
// if m > n, fbb = firstBranchBit(m, n) = m (big endian).
// p == mask(p, m) == mask(p, fbb) == mask(q, fbb) == mask(q, m)
// so mask(q, m) == p or matchPrefix(q, p, m)
// if m < n, is symmetric to the above.
//
// [<= proof]
// case m ==n && p == q. Then mask(p, fbb) == mask(q, fbb)
//
// case m > n && matchPrefix(q, p, m).
// fbb == firstBranchBit(m, n) == m (by m>n).
// mask(q, fbb) == mask(q, m) == p == mask(p, m) == mask(p, fbb)
//
// case m < n && matchPrefix(p, q, n) is symmetric.
}
================================================
FILE: go/callgraph/vta/internal/trie/bits_test.go
================================================
// Copyright 2021 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build go1.13
package trie
import (
"math/rand"
"testing"
)
func TestMask(t *testing.T) {
for _, c := range []struct {
p prefix
b bitpos
want prefix
}{
{
p: 0b00001000,
b: 0b00000100,
want: 0b00001011,
}, {
p: 0b01011011,
b: 0b00000000,
want: ^prefix(0),
}, {
p: 0b01011011,
b: 0b00000001,
want: 0b01011010,
}, {
p: 0b01011011,
b: 0b00000010,
want: 0b01011001,
}, {
p: 0b01011011,
b: 0b00000100,
want: 0b01011011,
}, {
p: 0b01011011,
b: 0b00001000,
want: 0b01010111,
}, {
p: 0b01011011,
b: 0b00010000,
want: 0b01001111,
}, {
p: 0b01011011,
b: 0b00100000,
want: 0b01011111,
}, {
p: 0b01011011,
b: 0b01000000,
want: 0b00111111,
}, {
p: 0b01011011,
b: 0b01000000,
want: 0b00111111,
}, {
p: 0b01011011,
b: 0b10000000,
want: 0b01111111,
},
} {
if got := mask(c.p, c.b); got != c.want {
t.Errorf("mask(%#b,%#b) got %#b. want %#b", c.p, c.b, got, c.want)
}
}
}
func TestMaskImpotent(t *testing.T) {
// test mask(mask(p, b), b) == mask(p,b)
for _, p := range []prefix{
0b0, 0b1, 0b100, ^prefix(0b0), ^prefix(0b10),
} {
for _, b := range []bitpos{
0, 0b1, 1 << 2, 1 << 63,
} {
once := mask(p, b)
twice := mask(once, b)
if once != twice {
t.Errorf("mask(mask(%#b,%#b), %#b) != mask(%#b,%#b) got %#b. want %#b",
p, b, b, p, b, twice, once)
}
}
}
}
func TestMatchPrefix(t *testing.T) {
for _, c := range []struct {
k prefix
p prefix
b bitpos
}{
{
k: 0b1000,
p: 0b1011,
b: 0b0100,
}, {
k: 0b1001,
p: 0b1011,
b: 0b0100,
}, {
k: 0b1010,
p: 0b1011,
b: 0b0100,
}, {
k: 0b1011,
p: 0b1011,
b: 0b0100,
}, {
k: 0b1100,
p: 0b1011,
b: 0b0100,
}, {
k: 0b1101,
p: 0b1011,
b: 0b0100,
}, {
k: 0b1110,
p: 0b1011,
b: 0b0100,
}, {
k: 0b1111,
p: 0b1011,
b: 0b0100,
},
} {
if !matchPrefix(c.k, c.p, c.b) {
t.Errorf("matchPrefix(%#b, %#b,%#b) should be true", c.k, c.p, c.b)
}
}
}
func TestNotMatchPrefix(t *testing.T) {
for _, c := range []struct {
k prefix
p prefix
b bitpos
}{
{
k: 0b0000,
p: 0b1011,
b: 0b0100,
}, {
k: 0b0010,
p: 0b1011,
b: 0b0100,
},
} {
if matchPrefix(c.k, c.p, c.b) {
t.Errorf("matchPrefix(%#b, %#b,%#b) should be false", c.k, c.p, c.b)
}
}
}
func TestBranchingBit(t *testing.T) {
for _, c := range []struct {
x prefix
y prefix
want bitpos
}{
{
x: 0b0000,
y: 0b1011,
want: 0b1000,
}, {
x: 0b1010,
y: 0b1011,
want: 0b0001,
}, {
x: 0b1011,
y: 0b1111,
want: 0b0100,
}, {
x: 0b1011,
y: 0b1001,
want: 0b0010,
},
} {
if got := branchingBit(c.x, c.y); got != c.want {
t.Errorf("branchingBit(%#b, %#b,) is not expected value. got %#b want %#b",
c.x, c.y, got, c.want)
}
}
}
func TestZeroBit(t *testing.T) {
for _, c := range []struct {
k prefix
b bitpos
}{
{
k: 0b1000,
b: 0b0100,
}, {
k: 0b1001,
b: 0b0100,
}, {
k: 0b1010,
b: 0b0100,
},
} {
if !zeroBit(c.k, c.b) {
t.Errorf("zeroBit(%#b, %#b) should be true", c.k, c.b)
}
}
}
func TestZeroBitFails(t *testing.T) {
for _, c := range []struct {
k prefix
b bitpos
}{
{
k: 0b1000,
b: 0b1000,
}, {
k: 0b1001,
b: 0b0001,
}, {
k: 0b1010,
b: 0b0010,
}, {
k: 0b1011,
b: 0b0001,
},
} {
if zeroBit(c.k, c.b) {
t.Errorf("zeroBit(%#b, %#b) should be false", c.k, c.b)
}
}
}
func TestOrd(t *testing.T) {
a := bitpos(0b0010)
b := bitpos(0b1000)
if ord(a, b) {
t.Errorf("ord(%#b, %#b) should be false", a, b)
}
if !ord(b, a) {
t.Errorf("ord(%#b, %#b) should be true", b, a)
}
if ord(a, a) {
t.Errorf("ord(%#b, %#b) should be false", a, a)
}
if !ord(a, 0) {
t.Errorf("ord(%#b, %#b) should be true", a, 0)
}
}
func TestPrefixesOverlapLemma(t *testing.T) {
// test
// mask(p, fbb) == mask(q, fbb)
// iff
// m > n && matchPrefix(q, p, m) or (note: big endian encoding)
// m < n && matchPrefix(p, q, n) or (note: big endian encoding)
// m ==n && p == q
// Case 1: mask(p, fbb) == mask(q, fbb) => m > n && matchPrefix(q, p, m)
m, n := bitpos(1<<2), bitpos(1<<1)
p, q := mask(0b100, m), mask(0b010, n)
if !(prefixesOverlap(p, m, q, n) && m > n && matchPrefix(q, p, m)) {
t.Errorf("prefixesOverlap(%#b, %#b, %#b, %#b) lemma does not hold",
p, m, q, n)
}
// Case 2: mask(p, fbb) == mask(q, fbb) => m < n && matchPrefix(p, q, n)
m, n = bitpos(1<<2), bitpos(1<<3)
p, q = mask(0b100, m), mask(0b1000, n)
if !(prefixesOverlap(p, m, q, n) && m < n && matchPrefix(p, q, n)) {
t.Errorf("prefixesOverlap(%#b, %#b, %#b, %#b) lemma does not hold",
p, m, q, n)
}
// Case 3: mask(p, fbb) == mask(q, fbb) => m < n && matchPrefix(p, q, n)
m, n = bitpos(1<<2), bitpos(1<<2)
p, q = mask(0b100, m), mask(0b001, n)
if !(prefixesOverlap(p, m, q, n) && m == n && p == q) {
t.Errorf("prefixesOverlap(%#b, %#b, %#b, %#b) lemma does not hold",
p, m, q, n)
}
// Case 4: mask(p, fbb) != mask(q, fbb)
m, n = bitpos(1<<1), bitpos(1<<1)
p, q = mask(0b100, m), mask(0b001, n)
if prefixesOverlap(p, m, q, n) ||
(m > n && matchPrefix(q, p, m)) ||
(m < n && matchPrefix(p, q, n)) ||
(m == n && p == q) {
t.Errorf("prefixesOverlap(%#b, %#b, %#b, %#b) lemma does not hold",
p, m, q, n)
}
// Do a few more random cases
r := rand.New(rand.NewSource(123))
N := 2000
for i := 0; i < N; i++ {
m := bitpos(1 << (r.Uint64() % (64 + 1)))
n := bitpos(1 << (r.Uint64() % (64 + 1)))
p := mask(prefix(r.Uint64()), m)
q := mask(prefix(r.Uint64()), n)
lhs := prefixesOverlap(p, m, q, n)
rhs := (m > n && matchPrefix(q, p, m)) ||
(m < n && matchPrefix(p, q, n)) ||
(m == n && p == q)
if lhs != rhs {
t.Errorf("prefixesOverlap(%#b, %#b, %#b, %#b) != got %v. want %v",
p, m, q, n, lhs, rhs)
}
}
}
================================================
FILE: go/callgraph/vta/internal/trie/builder.go
================================================
// Copyright 2021 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package trie
// Collision functions combine a left and right hand side (lhs and rhs) values
// the two values are associated with the same key and produces the value that
// will be stored for the key.
//
// Collision functions must be idempotent:
//
// collision(x, x) == x for all x.
//
// Collisions functions may be applied whenever a value is inserted
// or two maps are merged, or intersected.
type Collision func(lhs any, rhs any) any
// TakeLhs always returns the left value in a collision.
func TakeLhs(lhs, rhs any) any { return lhs }
// TakeRhs always returns the right hand side in a collision.
func TakeRhs(lhs, rhs any) any { return rhs }
// Builder creates new Map. Each Builder has a unique Scope.
//
// IMPORTANT: Nodes are hash-consed internally to reduce memory consumption. To
// support hash-consing Builders keep an internal Map of all of the Maps that they
// have created. To GC any of the Maps created by the Builder, all references to
// the Builder must be dropped. This includes MutMaps.
type Builder struct {
scope Scope
// hash-consing maps for each node type.
empty *empty
leaves map[leaf]*leaf
branches map[branch]*branch
// It may be possible to support more types of patricia tries
// (e.g. non-hash-consed) by making Builder an interface and abstracting
// the mkLeaf and mkBranch functions.
}
// NewBuilder creates a new Builder with a unique Scope.
func NewBuilder() *Builder {
s := newScope()
return &Builder{
scope: s,
empty: &empty{s},
leaves: make(map[leaf]*leaf),
branches: make(map[branch]*branch),
}
}
func (b *Builder) Scope() Scope { return b.scope }
// Rescope changes the builder's scope to a new unique Scope.
//
// Any Maps created using the previous scope need to be Cloned
// before any operation.
//
// This makes the old internals of the Builder eligible to be GC'ed.
func (b *Builder) Rescope() {
s := newScope()
b.scope = s
b.empty = &empty{s}
b.leaves = make(map[leaf]*leaf)
b.branches = make(map[branch]*branch)
}
// Empty is the empty map.
func (b *Builder) Empty() Map { return Map{b.Scope(), b.empty} }
// InsertWith inserts a new association from k to v into the Map m to create a new map
// in the current scope and handle collisions using the collision function c.
//
// This is roughly corresponds to updating a map[uint64]interface{} by:
//
// if _, ok := m[k]; ok { m[k] = c(m[k], v} else { m[k] = v}
//
// An insertion or update happened whenever Insert(m, ...) != m .
func (b *Builder) InsertWith(c Collision, m Map, k uint64, v any) Map {
m = b.Clone(m)
return Map{b.Scope(), b.insert(c, m.n, b.mkLeaf(key(k), v), false)}
}
// Inserts a new association from key to value into the Map m to create
// a new map in the current scope.
//
// If there was a previous value mapped by key, keep the previously mapped value.
// This is roughly corresponds to updating a map[uint64]interface{} by:
//
// if _, ok := m[k]; ok { m[k] = val }
//
// This is equivalent to b.Merge(m, b.Create({k: v})).
func (b *Builder) Insert(m Map, k uint64, v any) Map {
return b.InsertWith(TakeLhs, m, k, v)
}
// Updates a (key, value) in the map. This is roughly corresponds to
// updating a map[uint64]interface{} by:
//
// m[key] = val
func (b *Builder) Update(m Map, key uint64, val any) Map {
return b.InsertWith(TakeRhs, m, key, val)
}
// Merge two maps lhs and rhs to create a new map in the current scope.
//
// Whenever there is a key in both maps (a collision), the resulting value mapped by
// the key will be `c(lhs[key], rhs[key])`.
func (b *Builder) MergeWith(c Collision, lhs, rhs Map) Map {
lhs, rhs = b.Clone(lhs), b.Clone(rhs)
return Map{b.Scope(), b.merge(c, lhs.n, rhs.n)}
}
// Merge two maps lhs and rhs to create a new map in the current scope.
//
// Whenever there is a key in both maps (a collision), the resulting value mapped by
// the key will be the value in lhs `b.Collision(lhs[key], rhs[key])`.
func (b *Builder) Merge(lhs, rhs Map) Map {
return b.MergeWith(TakeLhs, lhs, rhs)
}
// Clone returns a Map that contains the same (key, value) elements
// within b.Scope(), i.e. return m if m.Scope() == b.Scope() or return
// a deep copy of m within b.Scope() otherwise.
func (b *Builder) Clone(m Map) Map {
if m.Scope() == b.Scope() {
return m
} else if m.n == nil {
return Map{b.Scope(), b.empty}
}
return Map{b.Scope(), b.clone(m.n)}
}
func (b *Builder) clone(n node) node {
switch n := n.(type) {
case *empty:
return b.empty
case *leaf:
return b.mkLeaf(n.k, n.v)
case *branch:
return b.mkBranch(n.prefix, n.branching, b.clone(n.left), b.clone(n.right))
default:
panic("unreachable")
}
}
// Remove a key from a Map m and return the resulting Map.
func (b *Builder) Remove(m Map, k uint64) Map {
m = b.Clone(m)
return Map{b.Scope(), b.remove(m.n, key(k))}
}
// Intersect Maps lhs and rhs and returns a map with all of the keys in
// both lhs and rhs and the value comes from lhs, i.e.
//
// {(k, lhs[k]) | k in lhs, k in rhs}.
func (b *Builder) Intersect(lhs, rhs Map) Map {
return b.IntersectWith(TakeLhs, lhs, rhs)
}
// IntersectWith take lhs and rhs and returns the intersection
// with the value coming from the collision function, i.e.
//
// {(k, c(lhs[k], rhs[k]) ) | k in lhs, k in rhs}.
//
// The elements of the resulting map are always { }
// for each key k that a key in both lhs and rhs.
func (b *Builder) IntersectWith(c Collision, lhs, rhs Map) Map {
l, r := b.Clone(lhs), b.Clone(rhs)
return Map{b.Scope(), b.intersect(c, l.n, r.n)}
}
// MutMap is a convenient wrapper for a Map and a *Builder that will be used to create
// new Maps from it.
type MutMap struct {
B *Builder
M Map
}
// MutEmpty is an empty MutMap for a builder.
func (b *Builder) MutEmpty() MutMap {
return MutMap{b, b.Empty()}
}
// Insert an element into the map using the collision function for the builder.
// Returns true if the element was inserted.
func (mm *MutMap) Insert(k uint64, v any) bool {
old := mm.M
mm.M = mm.B.Insert(old, k, v)
return old != mm.M
}
// Updates an element in the map. Returns true if the map was updated.
func (mm *MutMap) Update(k uint64, v any) bool {
old := mm.M
mm.M = mm.B.Update(old, k, v)
return old != mm.M
}
// Removes a key from the map. Returns true if the element was removed.
func (mm *MutMap) Remove(k uint64) bool {
old := mm.M
mm.M = mm.B.Remove(old, k)
return old != mm.M
}
// Merge another map into the current one using the collision function
// for the builder. Returns true if the map changed.
func (mm *MutMap) Merge(other Map) bool {
old := mm.M
mm.M = mm.B.Merge(old, other)
return old != mm.M
}
// Intersect another map into the current one using the collision function
// for the builder. Returns true if the map changed.
func (mm *MutMap) Intersect(other Map) bool {
old := mm.M
mm.M = mm.B.Intersect(old, other)
return old != mm.M
}
func (b *Builder) Create(m map[uint64]any) Map {
var leaves []*leaf
for k, v := range m {
leaves = append(leaves, b.mkLeaf(key(k), v))
}
return Map{b.Scope(), b.create(leaves)}
}
// Merge another map into the current one using the collision function
// for the builder. Returns true if the map changed.
func (mm *MutMap) MergeWith(c Collision, other Map) bool {
old := mm.M
mm.M = mm.B.MergeWith(c, old, other)
return old != mm.M
}
// creates a map for a collection of leaf nodes.
func (b *Builder) create(leaves []*leaf) node {
n := len(leaves)
if n == 0 {
return b.empty
} else if n == 1 {
return leaves[0]
}
// Note: we can do a more sophisticated algorithm by:
// - sorting the leaves ahead of time,
// - taking the prefix and branching bit of the min and max key,
// - binary searching for the branching bit,
// - splitting exactly where the branch will be, and
// - making the branch node for this prefix + branching bit.
// Skipping until this is a performance bottleneck.
m := n / 2 // (n >= 2) ==> 1 <= m < n
l, r := leaves[:m], leaves[m:]
return b.merge(nil, b.create(l), b.create(r))
}
// mkLeaf returns the hash-consed representative of (k, v) in the current scope.
func (b *Builder) mkLeaf(k key, v any) *leaf {
rep, ok := b.leaves[leaf{k, v}]
if !ok {
rep = &leaf{k, v} // heap-allocated copy
b.leaves[leaf{k, v}] = rep
}
return rep
}
// mkBranch returns the hash-consed representative of the tuple
//
// (prefix, branch, left, right)
//
// in the current scope.
func (b *Builder) mkBranch(p prefix, bp bitpos, left node, right node) *branch {
br := branch{
sz: left.size() + right.size(),
prefix: p,
branching: bp,
left: left,
right: right,
}
rep, ok := b.branches[br]
if !ok {
rep = new(branch) // heap-allocated copy
*rep = br
b.branches[br] = rep
}
return rep
}
// join two maps with prefixes p0 and p1 that are *known* to disagree.
func (b *Builder) join(p0 prefix, t0 node, p1 prefix, t1 node) *branch {
m := branchingBit(p0, p1)
var left, right node
if zeroBit(p0, m) {
left, right = t0, t1
} else {
left, right = t1, t0
}
prefix := mask(p0, m)
return b.mkBranch(prefix, m, left, right)
}
// collide two leaves with the same key to create a leaf
// with the collided value.
func (b *Builder) collide(c Collision, left, right *leaf) *leaf {
if left == right {
return left // c is idempotent: c(x, x) == x
}
val := left.v // keep the left value by default if c is nil
if c != nil {
val = c(left.v, right.v)
}
switch val {
case left.v:
return left
case right.v:
return right
default:
return b.mkLeaf(left.k, val)
}
}
// inserts a leaf l into a map m and returns the resulting map.
// When lhs is true, l is the left hand side in a collision.
// Both l and m are in the current scope.
func (b *Builder) insert(c Collision, m node, l *leaf, lhs bool) node {
switch m := m.(type) {
case *empty:
return l
case *leaf:
if m.k == l.k {
left, right := l, m
if !lhs {
left, right = right, left
}
return b.collide(c, left, right)
}
return b.join(prefix(l.k), l, prefix(m.k), m)
case *branch:
// fallthrough
}
// m is a branch
br := m.(*branch)
if !matchPrefix(prefix(l.k), br.prefix, br.branching) {
return b.join(prefix(l.k), l, br.prefix, br)
}
var left, right node
if zeroBit(prefix(l.k), br.branching) {
left, right = b.insert(c, br.left, l, lhs), br.right
} else {
left, right = br.left, b.insert(c, br.right, l, lhs)
}
if left == br.left && right == br.right {
return m
}
return b.mkBranch(br.prefix, br.branching, left, right)
}
// merge two maps in the current scope.
func (b *Builder) merge(c Collision, lhs, rhs node) node {
if lhs == rhs {
return lhs
}
switch lhs := lhs.(type) {
case *empty:
return rhs
case *leaf:
return b.insert(c, rhs, lhs, true)
case *branch:
switch rhs := rhs.(type) {
case *empty:
return lhs
case *leaf:
return b.insert(c, lhs, rhs, false)
case *branch:
// fallthrough
}
}
// Last remaining case is branch merging.
// For brevity, we adopt the Okasaki and Gill naming conventions
// for branching and prefixes.
s, t := lhs.(*branch), rhs.(*branch)
p, m := s.prefix, s.branching
q, n := t.prefix, t.branching
if m == n && p == q { // prefixes are identical.
left, right := b.merge(c, s.left, t.left), b.merge(c, s.right, t.right)
return b.mkBranch(p, m, left, right)
}
if !prefixesOverlap(p, m, q, n) {
return b.join(p, s, q, t) // prefixes are disjoint.
}
// prefixesOverlap(p, m, q, n) && !(m ==n && p == q)
// By prefixesOverlap(...), either:
// higher(m, n) && matchPrefix(q, p, m), or
// higher(n, m) && matchPrefix(p, q, n)
// So either s or t may can be merged with one branch or the other.
switch {
case ord(m, n) && zeroBit(q, m):
return b.mkBranch(p, m, b.merge(c, s.left, t), s.right)
case ord(m, n) && !zeroBit(q, m):
return b.mkBranch(p, m, s.left, b.merge(c, s.right, t))
case ord(n, m) && zeroBit(p, n):
return b.mkBranch(q, n, b.merge(c, s, t.left), t.right)
default:
return b.mkBranch(q, n, t.left, b.merge(c, s, t.right))
}
}
func (b *Builder) remove(m node, k key) node {
switch m := m.(type) {
case *empty:
return m
case *leaf:
if m.k == k {
return b.empty
}
return m
case *branch:
// fallthrough
}
br := m.(*branch)
kp := prefix(k)
if !matchPrefix(kp, br.prefix, br.branching) {
// The prefix does not match. kp is not in br.
return br
}
// the prefix matches. try to remove from the left or right branch.
left, right := br.left, br.right
if zeroBit(kp, br.branching) {
left = b.remove(left, k) // k may be in the left branch.
} else {
right = b.remove(right, k) // k may be in the right branch.
}
if left == br.left && right == br.right {
return br // no update
} else if _, ok := left.(*empty); ok {
return right // left updated and is empty.
} else if _, ok := right.(*empty); ok {
return left // right updated and is empty.
}
// Either left or right updated. Both left and right are not empty.
// The left and right branches still share the same prefix and disagree
// on the same branching bit. It is safe to directly create the branch.
return b.mkBranch(br.prefix, br.branching, left, right)
}
func (b *Builder) intersect(c Collision, l, r node) node {
if l == r {
return l
}
switch l := l.(type) {
case *empty:
return b.empty
case *leaf:
if rleaf := r.find(l.k); rleaf != nil {
return b.collide(c, l, rleaf)
}
return b.empty
case *branch:
switch r := r.(type) {
case *empty:
return b.empty
case *leaf:
if lleaf := l.find(r.k); lleaf != nil {
return b.collide(c, lleaf, r)
}
return b.empty
case *branch:
// fallthrough
}
}
// Last remaining case is branch intersection.
s, t := l.(*branch), r.(*branch)
p, m := s.prefix, s.branching
q, n := t.prefix, t.branching
if m == n && p == q {
// prefixes are identical.
left, right := b.intersect(c, s.left, t.left), b.intersect(c, s.right, t.right)
if _, ok := left.(*empty); ok {
return right
} else if _, ok := right.(*empty); ok {
return left
}
// The left and right branches are both non-empty.
// They still share the same prefix and disagree on the same branching bit.
// It is safe to directly create the branch.
return b.mkBranch(p, m, left, right)
}
if !prefixesOverlap(p, m, q, n) {
return b.empty // The prefixes share no keys.
}
// prefixesOverlap(p, m, q, n) && !(m ==n && p == q)
// By prefixesOverlap(...), either:
// ord(m, n) && matchPrefix(q, p, m), or
// ord(n, m) && matchPrefix(p, q, n)
// So either s or t may be a strict subtree of the other.
var lhs, rhs node
switch {
case ord(m, n) && zeroBit(q, m):
lhs, rhs = s.left, t
case ord(m, n) && !zeroBit(q, m):
lhs, rhs = s.right, t
case ord(n, m) && zeroBit(p, n):
lhs, rhs = s, t.left
default:
lhs, rhs = s, t.right
}
return b.intersect(c, lhs, rhs)
}
================================================
FILE: go/callgraph/vta/internal/trie/op_test.go
================================================
// Copyright 2021 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package trie_test
import (
"fmt"
"math/rand"
"reflect"
"testing"
"time"
"golang.org/x/tools/go/callgraph/vta/internal/trie"
"maps"
)
// This file tests trie.Map by cross checking operations on a collection of
// trie.Map's against a collection of map[uint64]interface{}. This includes
// both limited fuzz testing for correctness and benchmarking.
// mapCollection is effectively a []map[uint64]interface{}.
// These support operations being applied to the i'th maps.
type mapCollection interface {
Elements() []map[uint64]any
DeepEqual(l, r int) bool
Lookup(id int, k uint64) (any, bool)
Insert(id int, k uint64, v any)
Update(id int, k uint64, v any)
Remove(id int, k uint64)
Intersect(l int, r int)
Merge(l int, r int)
Clear(id int)
Average(l int, r int)
Assign(l int, r int)
}
// opCode of an operation.
type opCode int
const (
deepEqualsOp opCode = iota
lookupOp
insert
update
remove
merge
intersect
clear
takeAverage
assign
)
func (op opCode) String() string {
switch op {
case deepEqualsOp:
return "DE"
case lookupOp:
return "LO"
case insert:
return "IN"
case update:
return "UP"
case remove:
return "RE"
case merge:
return "ME"
case intersect:
return "IT"
case clear:
return "CL"
case takeAverage:
return "AV"
case assign:
return "AS"
default:
return "??"
}
}
// A mapCollection backed by MutMaps.
type trieCollection struct {
b *trie.Builder
tries []trie.MutMap
}
func (c *trieCollection) Elements() []map[uint64]any {
var maps []map[uint64]any
for _, m := range c.tries {
maps = append(maps, trie.Elems(m.M))
}
return maps
}
func (c *trieCollection) Eq(id int, m map[uint64]any) bool {
elems := trie.Elems(c.tries[id].M)
return !reflect.DeepEqual(elems, m)
}
func (c *trieCollection) Lookup(id int, k uint64) (any, bool) {
return c.tries[id].M.Lookup(k)
}
func (c *trieCollection) DeepEqual(l, r int) bool {
return c.tries[l].M.DeepEqual(c.tries[r].M)
}
func (c *trieCollection) Add() {
c.tries = append(c.tries, c.b.MutEmpty())
}
func (c *trieCollection) Insert(id int, k uint64, v any) {
c.tries[id].Insert(k, v)
}
func (c *trieCollection) Update(id int, k uint64, v any) {
c.tries[id].Update(k, v)
}
func (c *trieCollection) Remove(id int, k uint64) {
c.tries[id].Remove(k)
}
func (c *trieCollection) Intersect(l int, r int) {
c.tries[l].Intersect(c.tries[r].M)
}
func (c *trieCollection) Merge(l int, r int) {
c.tries[l].Merge(c.tries[r].M)
}
func (c *trieCollection) Average(l int, r int) {
c.tries[l].MergeWith(average, c.tries[r].M)
}
func (c *trieCollection) Clear(id int) {
c.tries[id] = c.b.MutEmpty()
}
func (c *trieCollection) Assign(l, r int) {
c.tries[l] = c.tries[r]
}
func average(x any, y any) any {
if x, ok := x.(float32); ok {
if y, ok := y.(float32); ok {
return (x + y) / 2.0
}
}
return x
}
type builtinCollection []map[uint64]any
func (c builtinCollection) Elements() []map[uint64]any {
return c
}
func (c builtinCollection) Lookup(id int, k uint64) (any, bool) {
v, ok := c[id][k]
return v, ok
}
func (c builtinCollection) DeepEqual(l, r int) bool {
return reflect.DeepEqual(c[l], c[r])
}
func (c builtinCollection) Insert(id int, k uint64, v any) {
if _, ok := c[id][k]; !ok {
c[id][k] = v
}
}
func (c builtinCollection) Update(id int, k uint64, v any) {
c[id][k] = v
}
func (c builtinCollection) Remove(id int, k uint64) {
delete(c[id], k)
}
func (c builtinCollection) Intersect(l int, r int) {
result := map[uint64]any{}
for k, v := range c[l] {
if _, ok := c[r][k]; ok {
result[k] = v
}
}
c[l] = result
}
func (c builtinCollection) Merge(l int, r int) {
result := map[uint64]any{}
maps.Copy(result, c[r])
maps.Copy(result, c[l])
c[l] = result
}
func (c builtinCollection) Average(l int, r int) {
avg := map[uint64]any{}
for k, lv := range c[l] {
if rv, ok := c[r][k]; ok {
avg[k] = average(lv, rv)
} else {
avg[k] = lv // add elements just in l
}
}
for k, rv := range c[r] {
if _, ok := c[l][k]; !ok {
avg[k] = rv // add elements just in r
}
}
c[l] = avg
}
func (c builtinCollection) Assign(l, r int) {
m := map[uint64]any{}
maps.Copy(m, c[r])
c[l] = m
}
func (c builtinCollection) Clear(id int) {
c[id] = map[uint64]any{}
}
func newTriesCollection(size int) *trieCollection {
tc := &trieCollection{
b: trie.NewBuilder(),
tries: make([]trie.MutMap, size),
}
for i := range size {
tc.tries[i] = tc.b.MutEmpty()
}
return tc
}
func newMapsCollection(size int) *builtinCollection {
maps := make(builtinCollection, size)
for i := range size {
maps[i] = map[uint64]any{}
}
return &maps
}
// operation on a map collection.
type operation struct {
code opCode
l, r int
k uint64
v float32
}
// Apply the operation to maps.
func (op operation) Apply(maps mapCollection) any {
type lookupresult struct {
v any
ok bool
}
switch op.code {
case deepEqualsOp:
return maps.DeepEqual(op.l, op.r)
case lookupOp:
v, ok := maps.Lookup(op.l, op.k)
return lookupresult{v, ok}
case insert:
maps.Insert(op.l, op.k, op.v)
case update:
maps.Update(op.l, op.k, op.v)
case remove:
maps.Remove(op.l, op.k)
case merge:
maps.Merge(op.l, op.r)
case intersect:
maps.Intersect(op.l, op.r)
case clear:
maps.Clear(op.l)
case takeAverage:
maps.Average(op.l, op.r)
case assign:
maps.Assign(op.l, op.r)
}
return nil
}
// Returns a collection of op codes with dist[op] copies of op.
func distribution(dist map[opCode]int) []opCode {
var codes []opCode
for op, n := range dist {
for range n {
codes = append(codes, op)
}
}
return codes
}
// options for generating a random operation.
type options struct {
maps int
maxKey uint64
maxVal int
codes []opCode
}
// returns a random operation using r as a source of randomness.
func randOperator(r *rand.Rand, opts options) operation {
id := func() int { return r.Intn(opts.maps) }
key := func() uint64 { return r.Uint64() % opts.maxKey }
val := func() float32 { return float32(r.Intn(opts.maxVal)) }
switch code := opts.codes[r.Intn(len(opts.codes))]; code {
case lookupOp, remove:
return operation{code: code, l: id(), k: key()}
case insert, update:
return operation{code: code, l: id(), k: key(), v: val()}
case deepEqualsOp, merge, intersect, takeAverage, assign:
return operation{code: code, l: id(), r: id()}
case clear:
return operation{code: code, l: id()}
default:
panic("Invalid op code")
}
}
func randOperators(r *rand.Rand, numops int, opts options) []operation {
ops := make([]operation, numops)
for i := range numops {
ops[i] = randOperator(r, opts)
}
return ops
}
// TestOperations applies a series of random operations to collection of
// trie.MutMaps and map[uint64]interface{}. It tests for the maps being equal.
func TestOperations(t *testing.T) {
seed := time.Now().UnixNano()
s := rand.NewSource(seed)
r := rand.New(s)
t.Log("seed: ", seed)
size := 10
N := 100000
ops := randOperators(r, N, options{
maps: size,
maxKey: 128,
maxVal: 100,
codes: distribution(map[opCode]int{
deepEqualsOp: 1,
lookupOp: 10,
insert: 10,
update: 10,
remove: 10,
merge: 10,
intersect: 10,
clear: 2,
takeAverage: 5,
assign: 5,
}),
})
var tries mapCollection = newTriesCollection(size)
var maps mapCollection = newMapsCollection(size)
check := func() error {
if got, want := tries.Elements(), maps.Elements(); !reflect.DeepEqual(got, want) {
return fmt.Errorf("elements of tries and maps and tries differed. got %v want %v", got, want)
}
return nil
}
for i, op := range ops {
got, want := op.Apply(tries), op.Apply(maps)
if got != want {
t.Errorf("op[%d]: (%v).Apply(%v) != (%v).Apply(%v). got %v want %v",
i, op, tries, op, maps, got, want)
}
}
if err := check(); err != nil {
t.Errorf("%d operators failed with %s", size, err)
t.Log("Rerunning with more checking")
tries, maps = newTriesCollection(size), newMapsCollection(size)
for i, op := range ops {
op.Apply(tries)
op.Apply(maps)
if err := check(); err != nil {
t.Fatalf("Failed first on op[%d]=%v: %v", i, op, err)
}
}
}
}
func run(b *testing.B, opts options, seed int64, mk func(int) mapCollection) {
r := rand.New(rand.NewSource(seed))
ops := randOperators(r, b.N, opts)
maps := mk(opts.maps)
for _, op := range ops {
op.Apply(maps)
}
}
var standard options = options{
maps: 10,
maxKey: 128,
maxVal: 100,
codes: distribution(map[opCode]int{
deepEqualsOp: 1,
lookupOp: 20,
insert: 20,
update: 20,
remove: 20,
merge: 10,
intersect: 10,
clear: 1,
takeAverage: 5,
assign: 20,
}),
}
func BenchmarkTrieStandard(b *testing.B) {
run(b, standard, 123, func(size int) mapCollection {
return newTriesCollection(size)
})
}
func BenchmarkMapsStandard(b *testing.B) {
run(b, standard, 123, func(size int) mapCollection {
return newMapsCollection(size)
})
}
var smallWide options = options{
maps: 100,
maxKey: 100,
maxVal: 8,
codes: distribution(map[opCode]int{
deepEqualsOp: 0,
lookupOp: 0,
insert: 30,
update: 20,
remove: 0,
merge: 10,
intersect: 0,
clear: 1,
takeAverage: 0,
assign: 30,
}),
}
func BenchmarkTrieSmallWide(b *testing.B) {
run(b, smallWide, 456, func(size int) mapCollection {
return newTriesCollection(size)
})
}
func BenchmarkMapsSmallWide(b *testing.B) {
run(b, smallWide, 456, func(size int) mapCollection {
return newMapsCollection(size)
})
}
================================================
FILE: go/callgraph/vta/internal/trie/scope.go
================================================
// Copyright 2021 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package trie
import (
"strconv"
"sync/atomic"
)
// Scope represents a distinct collection of maps.
// Maps with the same Scope can be equal. Maps in different scopes are distinct.
// Each Builder creates maps within a unique Scope.
type Scope struct {
id int32
}
var nextScopeId int32
func newScope() Scope {
id := atomic.AddInt32(&nextScopeId, 1)
return Scope{id: id}
}
func (s Scope) String() string {
return strconv.Itoa(int(s.id))
}
================================================
FILE: go/callgraph/vta/internal/trie/trie.go
================================================
// Copyright 2021 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// trie implements persistent Patricia trie maps.
//
// Each Map is effectively a map from uint64 to interface{}. Patricia tries are
// a form of radix tree that are particularly appropriate when many maps will be
// created, merged together and large amounts of sharing are expected (e.g.
// environment abstract domains in program analysis).
//
// This implementation closely follows the paper:
//
// C. Okasaki and A. Gill, “Fast mergeable integer maps,” in ACM SIGPLAN
// Workshop on ML, September 1998, pp. 77–86.
//
// Each Map is immutable and can be read from concurrently. The map does not
// guarantee that the value pointed to by the interface{} value is not updated
// concurrently.
//
// These Maps are optimized for situations where there will be many maps created at
// with a high degree of sharing and combining of maps together. If you do not expect,
// significant amount of sharing, the builtin map[T]U is much better choice!
//
// Each Map is created by a Builder. Each Builder has a unique Scope and each node is
// created within this scope. Maps x and y are == if they contains the same
// (key,value) mappings and have equal scopes.
//
// Internally these are big endian Patricia trie nodes, and the keys are sorted.
package trie
import (
"fmt"
"strings"
)
// Map is effectively a finite mapping from uint64 keys to interface{} values.
// Maps are immutable and can be read from concurrently.
//
// Notes on concurrency:
// - A Map value itself is an interface and assignments to a Map value can race.
// - Map does not guarantee that the value pointed to by the interface{} value
// is not updated concurrently.
type Map struct {
s Scope
n node
}
func (m Map) Scope() Scope {
return m.s
}
func (m Map) Size() int {
if m.n == nil {
return 0
}
return m.n.size()
}
func (m Map) Lookup(k uint64) (any, bool) {
if m.n != nil {
if leaf := m.n.find(key(k)); leaf != nil {
return leaf.v, true
}
}
return nil, false
}
// Converts the map into a {: [, ...]} string. This uses the default
// %s string conversion for .
func (m Map) String() string {
var kvs []string
m.Range(func(u uint64, i any) bool {
kvs = append(kvs, fmt.Sprintf("%d: %s", u, i))
return true
})
return fmt.Sprintf("{%s}", strings.Join(kvs, ", "))
}
// Range over the leaf (key, value) pairs in the map in order and
// applies cb(key, value) to each. Stops early if cb returns false.
// Returns true if all elements were visited without stopping early.
func (m Map) Range(cb func(uint64, any) bool) bool {
if m.n != nil {
return m.n.visit(cb)
}
return true
}
// DeepEqual returns true if m and other contain the same (k, v) mappings
// [regardless of Scope].
//
// Equivalently m.DeepEqual(other) <=> reflect.DeepEqual(Elems(m), Elems(other))
func (m Map) DeepEqual(other Map) bool {
if m.Scope() == other.Scope() {
return m.n == other.n
}
if (m.n == nil) || (other.n == nil) {
return m.Size() == 0 && other.Size() == 0
}
return m.n.deepEqual(other.n)
}
// Elems are the (k,v) elements in the Map as a map[uint64]interface{}
func Elems(m Map) map[uint64]any {
dest := make(map[uint64]any, m.Size())
m.Range(func(k uint64, v any) bool {
dest[k] = v
return true
})
return dest
}
// node is an internal node within a trie map.
// A node is either empty, a leaf or a branch.
type node interface {
size() int
// visit the leaves (key, value) pairs in the map in order and
// applies cb(key, value) to each. Stops early if cb returns false.
// Returns true if all elements were visited without stopping early.
visit(cb func(uint64, any) bool) bool
// Two nodes contain the same elements regardless of scope.
deepEqual(node) bool
// find the leaf for the given key value or nil if it is not present.
find(k key) *leaf
// implementations must implement this.
nodeImpl()
}
// empty represents the empty map within a scope.
//
// The current builder ensure
type empty struct {
s Scope
}
// leaf represents a single pair.
type leaf struct {
k key
v any
}
// branch represents a tree node within the Patricia trie.
//
// All keys within the branch match a `prefix` of the key
// up to a `branching` bit, and the left and right nodes
// contain keys that disagree on the bit at the `branching` bit.
type branch struct {
sz int // size. cached for O(1) lookup
prefix prefix // == mask(p0, branching) for some p0
branching bitpos
// Invariants:
// - neither is nil.
// - neither is *empty.
// - all keys in left are <= p.
// - all keys in right are > p.
left, right node
}
// all of these types are Maps.
var _ node = &empty{}
var _ node = &leaf{}
var _ node = &branch{}
func (*empty) nodeImpl() {}
func (*leaf) nodeImpl() {}
func (*branch) nodeImpl() {}
func (*empty) find(k key) *leaf { return nil }
func (l *leaf) find(k key) *leaf {
if k == l.k {
return l
}
return nil
}
func (br *branch) find(k key) *leaf {
kp := prefix(k)
if !matchPrefix(kp, br.prefix, br.branching) {
return nil
}
if zeroBit(kp, br.branching) {
return br.left.find(k)
}
return br.right.find(k)
}
func (*empty) size() int { return 0 }
func (*leaf) size() int { return 1 }
func (br *branch) size() int { return br.sz }
func (*empty) deepEqual(m node) bool {
_, ok := m.(*empty)
return ok
}
func (l *leaf) deepEqual(m node) bool {
if m, ok := m.(*leaf); ok {
return m == l || (l.k == m.k && l.v == m.v)
}
return false
}
func (br *branch) deepEqual(m node) bool {
if m, ok := m.(*branch); ok {
if br == m {
return true
}
return br.sz == m.sz && br.branching == m.branching && br.prefix == m.prefix &&
br.left.deepEqual(m.left) && br.right.deepEqual(m.right)
}
// if m is not a branch, m contains 0 or 1 elem.
// br contains at least 2 keys that disagree on a prefix.
return false
}
func (*empty) visit(cb func(uint64, any) bool) bool {
return true
}
func (l *leaf) visit(cb func(uint64, any) bool) bool {
return cb(uint64(l.k), l.v)
}
func (br *branch) visit(cb func(uint64, any) bool) bool {
if !br.left.visit(cb) {
return false
}
return br.right.visit(cb)
}
================================================
FILE: go/callgraph/vta/internal/trie/trie_test.go
================================================
// Copyright 2021 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build go1.13
package trie
import (
"reflect"
"strconv"
"testing"
)
func TestScope(t *testing.T) {
def := Scope{}
s0, s1 := newScope(), newScope()
if s0 == def || s1 == def {
t.Error("newScope() should never be == to the default scope")
}
if s0 == s1 {
t.Errorf("newScope() %q and %q should not be ==", s0, s1)
}
if s0.id == 0 {
t.Error("s0.id is 0")
}
if s1.id == 0 {
t.Error("s1.id is 0")
}
got := s0.String()
if _, err := strconv.Atoi(got); err != nil {
t.Errorf("scope{%s}.String() is not an int: got %s with error %s", s0, got, err)
}
}
func TestCollision(t *testing.T) {
var x any = 1
var y any = 2
if v := TakeLhs(x, y); v != x {
t.Errorf("TakeLhs(%s, %s) got %s. want %s", x, y, v, x)
}
if v := TakeRhs(x, y); v != y {
t.Errorf("TakeRhs(%s, %s) got %s. want %s", x, y, v, y)
}
}
func TestDefault(t *testing.T) {
def := Map{}
if def.Size() != 0 {
t.Errorf("default node has non-0 size %d", def.Size())
}
if want, got := (Scope{}), def.Scope(); got != want {
t.Errorf("default is in a non default scope (%s) from b (%s)", got, want)
}
if v, ok := def.Lookup(123); !(v == nil && !ok) {
t.Errorf("Scope{}.Lookup() = (%s, %v) not (nil, false)", v, ok)
}
if !def.Range(func(k uint64, v any) bool {
t.Errorf("Scope{}.Range() called it callback on %d:%s", k, v)
return true
}) {
t.Error("Scope{}.Range() always iterates through all elements")
}
if got, want := def.String(), "{}"; got != want {
t.Errorf("Scope{}.String() got %s. want %s", got, want)
}
b := NewBuilder()
if def == b.Empty() {
t.Error("Scope{} == to an empty node from a builder")
}
if b.Clone(def) != b.Empty() {
t.Error("b.Clone(Scope{}) should equal b.Empty()")
}
if !def.DeepEqual(b.Empty()) {
t.Error("Scope{}.DeepEqual(b.Empty()) should hold")
}
}
func TestBuilders(t *testing.T) {
b0, b1 := NewBuilder(), NewBuilder()
if b0.Scope() == b1.Scope() {
t.Errorf("builders have the same scope %s", b0.Scope())
}
if b0.Empty() == b1.Empty() {
t.Errorf("empty nodes from different scopes are disequal")
}
if !b0.Empty().DeepEqual(b1.Empty()) {
t.Errorf("empty nodes from different scopes are not DeepEqual")
}
clone := b1.Clone(b0.Empty())
if clone != b1.Empty() {
t.Errorf("Clone() empty nodes %v != %v", clone, b1.Empty())
}
}
func TestEmpty(t *testing.T) {
b := NewBuilder()
e := b.Empty()
if e.Size() != 0 {
t.Errorf("empty nodes has non-0 size %d", e.Size())
}
if e.Scope() != b.Scope() {
t.Errorf("b.Empty() is in a different scope (%s) from b (%s)", e.Scope(), b.Scope())
}
if v, ok := e.Lookup(123); !(v == nil && !ok) {
t.Errorf("empty.Lookup() = (%s, %v) not (nil, false)", v, ok)
}
if l := e.n.find(123); l != nil {
t.Errorf("empty.find(123) got %v. want nil", l)
}
e.Range(func(k uint64, v any) bool {
t.Errorf("empty.Range() called it callback on %d:%s", k, v)
return true
})
want := "{}"
if got := e.String(); got != want {
t.Errorf("empty.String(123) got %s. want %s", got, want)
}
}
func TestCreate(t *testing.T) {
// The node orders are printed in lexicographic little-endian.
b := NewBuilder()
for _, c := range []struct {
m map[uint64]any
want string
}{
{
map[uint64]any{},
"{}",
},
{
map[uint64]any{1: "a"},
"{1: a}",
},
{
map[uint64]any{2: "b", 1: "a"},
"{1: a, 2: b}",
},
{
map[uint64]any{1: "x", 4: "y", 5: "z"},
"{1: x, 4: y, 5: z}",
},
} {
m := b.Create(c.m)
if got := m.String(); got != c.want {
t.Errorf("Create(%v) got %q. want %q ", c.m, got, c.want)
}
}
}
func TestElems(t *testing.T) {
b := NewBuilder()
for _, orig := range []map[uint64]any{
{},
{1: "a"},
{1: "a", 2: "b"},
{1: "x", 4: "y", 5: "z"},
{1: "x", 4: "y", 5: "z", 123: "abc"},
} {
m := b.Create(orig)
if elems := Elems(m); !reflect.DeepEqual(orig, elems) {
t.Errorf("Elems(%v) got %q. want %q ", m, elems, orig)
}
}
}
func TestRange(t *testing.T) {
b := NewBuilder()
m := b.Create(map[uint64]any{1: "x", 3: "y", 5: "z", 6: "stop", 8: "a"})
calls := 0
cb := func(k uint64, v any) bool {
t.Logf("visiting (%d, %v)", k, v)
calls++
return k%2 != 0 // stop after the first even number.
}
// The nodes are visited in increasing order.
all := m.Range(cb)
if all {
t.Error("expected to stop early")
}
want := 4
if calls != want {
t.Errorf("# of callbacks (%d) was expected to equal %d (1 + # of evens)",
calls, want)
}
}
func TestDeepEqual(t *testing.T) {
for _, m := range []map[uint64]any{
{},
{1: "x"},
{1: "x", 2: "y"},
} {
l := NewBuilder().Create(m)
r := NewBuilder().Create(m)
if !l.DeepEqual(r) {
t.Errorf("Expect %v to be DeepEqual() to %v", l, r)
}
}
}
func TestNotDeepEqual(t *testing.T) {
for _, c := range []struct {
left map[uint64]any
right map[uint64]any
}{
{
map[uint64]any{1: "x"},
map[uint64]any{},
},
{
map[uint64]any{},
map[uint64]any{1: "y"},
},
{
map[uint64]any{1: "x"},
map[uint64]any{1: "y"},
},
{
map[uint64]any{1: "x"},
map[uint64]any{1: "x", 2: "Y"},
},
{
map[uint64]any{1: "x", 2: "Y"},
map[uint64]any{1: "x"},
},
{
map[uint64]any{1: "x", 2: "y"},
map[uint64]any{1: "x", 2: "Y"},
},
} {
l := NewBuilder().Create(c.left)
r := NewBuilder().Create(c.right)
if l.DeepEqual(r) {
t.Errorf("Expect %v to be !DeepEqual() to %v", l, r)
}
}
}
func TestMerge(t *testing.T) {
b := NewBuilder()
for _, c := range []struct {
left map[uint64]any
right map[uint64]any
want string
}{
{
map[uint64]any{},
map[uint64]any{},
"{}",
},
{
map[uint64]any{},
map[uint64]any{1: "a"},
"{1: a}",
},
{
map[uint64]any{1: "a"},
map[uint64]any{},
"{1: a}",
},
{
map[uint64]any{1: "a", 2: "b"},
map[uint64]any{},
"{1: a, 2: b}",
},
{
map[uint64]any{1: "x"},
map[uint64]any{1: "y"},
"{1: x}", // default collision is left
},
{
map[uint64]any{1: "x"},
map[uint64]any{2: "y"},
"{1: x, 2: y}",
},
{
map[uint64]any{4: "y", 5: "z"},
map[uint64]any{1: "x"},
"{1: x, 4: y, 5: z}",
},
{
map[uint64]any{1: "x", 5: "z"},
map[uint64]any{4: "y"},
"{1: x, 4: y, 5: z}",
},
{
map[uint64]any{1: "x", 4: "y"},
map[uint64]any{5: "z"},
"{1: x, 4: y, 5: z}",
},
{
map[uint64]any{1: "a", 4: "c"},
map[uint64]any{2: "b", 5: "d"},
"{1: a, 2: b, 4: c, 5: d}",
},
{
map[uint64]any{1: "a", 4: "c"},
map[uint64]any{2: "b", 5 + 8: "d"},
"{1: a, 2: b, 4: c, 13: d}",
},
{
map[uint64]any{2: "b", 5 + 8: "d"},
map[uint64]any{1: "a", 4: "c"},
"{1: a, 2: b, 4: c, 13: d}",
},
{
map[uint64]any{1: "a", 4: "c"},
map[uint64]any{2: "b", 5 + 8: "d"},
"{1: a, 2: b, 4: c, 13: d}",
},
{
map[uint64]any{2: "b", 5 + 8: "d"},
map[uint64]any{1: "a", 4: "c"},
"{1: a, 2: b, 4: c, 13: d}",
},
{
map[uint64]any{2: "b", 5 + 8: "d"},
map[uint64]any{2: "", 3: "a"},
"{2: b, 3: a, 13: d}",
},
{
// crafted for `!prefixesOverlap(p, m, q, n)`
left: map[uint64]any{1: "a", 2 + 1: "b"},
right: map[uint64]any{4 + 1: "c", 4 + 2: "d"},
// p: 5, m: 2 q: 1, n: 2
want: "{1: a, 3: b, 5: c, 6: d}",
},
{
// crafted for `ord(m, n) && !zeroBit(q, m)`
left: map[uint64]any{8 + 2 + 1: "a", 16 + 4: "b"},
right: map[uint64]any{16 + 8 + 2 + 1: "c", 16 + 8 + 4 + 2 + 1: "d"},
// left: p: 15, m: 16
// right: q: 27, n: 4
want: "{11: a, 20: b, 27: c, 31: d}",
},
{
// crafted for `ord(n, m) && !zeroBit(p, n)`
// p: 6, m: 1 q: 5, n: 2
left: map[uint64]any{4 + 2: "b", 4 + 2 + 1: "c"},
right: map[uint64]any{4: "a", 4 + 2 + 1: "dropped"},
want: "{4: a, 6: b, 7: c}",
},
} {
l, r := b.Create(c.left), b.Create(c.right)
m := b.Merge(l, r)
if got := m.String(); got != c.want {
t.Errorf("Merge(%s, %s) got %q. want %q ", l, r, got, c.want)
}
}
}
func TestIntersect(t *testing.T) {
// Most of the test cases go after specific branches of intersect.
b := NewBuilder()
for _, c := range []struct {
left map[uint64]any
right map[uint64]any
want string
}{
{
left: map[uint64]any{10: "a", 39: "b"},
right: map[uint64]any{10: "A", 39: "B", 75: "C"},
want: "{10: a, 39: b}",
},
{
left: map[uint64]any{10: "a", 39: "b"},
right: map[uint64]any{},
want: "{}",
},
{
left: map[uint64]any{},
right: map[uint64]any{10: "A", 39: "B", 75: "C"},
want: "{}",
},
{ // m == n && p == q && left.(*empty) case
left: map[uint64]any{4: 1, 6: 3, 10: 8, 15: "on left"},
right: map[uint64]any{0: 8, 7: 6, 11: 0, 15: "on right"},
want: "{15: on left}",
},
{ // m == n && p == q && right.(*empty) case
left: map[uint64]any{0: "on left", 1: 2, 2: 3, 3: 1, 7: 3},
right: map[uint64]any{0: "on right", 5: 1, 6: 8},
want: "{0: on left}",
},
{ // m == n && p == q && both left and right are not empty
left: map[uint64]any{1: "a", 2: "b", 3: "c"},
right: map[uint64]any{0: "A", 1: "B", 2: "C"},
want: "{1: a, 2: b}",
},
{ // m == n && p == q && both left and right are not empty
left: map[uint64]any{1: "a", 2: "b", 3: "c"},
right: map[uint64]any{0: "A", 1: "B", 2: "C"},
want: "{1: a, 2: b}",
},
{ // !prefixesOverlap(p, m, q, n)
// p = 1, m = 2, q = 5, n = 2
left: map[uint64]any{0b001: 1, 0b011: 3},
right: map[uint64]any{0b100: 4, 0b111: 7},
want: "{}",
},
{ // ord(m, n) && zeroBit(q, m)
// p = 3, m = 4, q = 0, n = 1
left: map[uint64]any{0b010: 2, 0b101: 5},
right: map[uint64]any{0b000: 0, 0b001: 1},
want: "{}",
},
{ // ord(m, n) && !zeroBit(q, m)
// p = 29, m = 2, q = 30, n = 1
left: map[uint64]any{
0b11101: "29",
0b11110: "30",
},
right: map[uint64]any{
0b11110: "30 on right",
0b11111: "31",
},
want: "{30: 30}",
},
{ // ord(n, m) && zeroBit(p, n)
// p = 5, m = 2, q = 3, n = 4
left: map[uint64]any{0b000: 0, 0b001: 1},
right: map[uint64]any{0b010: 2, 0b101: 5},
want: "{}",
},
{ // default case
// p = 5, m = 2, q = 3, n = 4
left: map[uint64]any{0b100: 1, 0b110: 3},
right: map[uint64]any{0b000: 8, 0b111: 6},
want: "{}",
},
} {
l, r := b.Create(c.left), b.Create(c.right)
m := b.Intersect(l, r)
if got := m.String(); got != c.want {
t.Errorf("Intersect(%s, %s) got %q. want %q ", l, r, got, c.want)
}
}
}
func TestIntersectWith(t *testing.T) {
b := NewBuilder()
l := b.Create(map[uint64]any{10: 2.0, 39: 32.0})
r := b.Create(map[uint64]any{10: 6.0, 39: 10.0, 75: 1.0})
prodIfDifferent := func(x any, y any) any {
if x, ok := x.(float64); ok {
if y, ok := y.(float64); ok {
if x == y {
return x
}
return x * y
}
}
return x
}
m := b.IntersectWith(prodIfDifferent, l, r)
want := "{10: %!s(float64=12), 39: %!s(float64=320)}"
if got := m.String(); got != want {
t.Errorf("IntersectWith(min, %s, %s) got %q. want %q ", l, r, got, want)
}
}
func TestRemove(t *testing.T) {
// Most of the test cases go after specific branches of intersect.
b := NewBuilder()
for _, c := range []struct {
m map[uint64]any
key uint64
want string
}{
{map[uint64]any{}, 10, "{}"},
{map[uint64]any{10: "a"}, 10, "{}"},
{map[uint64]any{39: "b"}, 10, "{39: b}"},
// Branch cases:
// !matchPrefix(kp, br.prefix, br.branching)
{map[uint64]any{10: "a", 39: "b"}, 128, "{10: a, 39: b}"},
// case: left == br.left && right == br.right
{map[uint64]any{10: "a", 39: "b"}, 16, "{10: a, 39: b}"},
// left updated and is empty.
{map[uint64]any{10: "a", 39: "b"}, 10, "{39: b}"},
// right updated and is empty.
{map[uint64]any{10: "a", 39: "b"}, 39, "{10: a}"},
// final b.mkBranch(...) case.
{map[uint64]any{10: "a", 39: "b", 128: "c"}, 39, "{10: a, 128: c}"},
} {
pre := b.Create(c.m)
post := b.Remove(pre, c.key)
if got := post.String(); got != c.want {
t.Errorf("Remove(%s, %d) got %q. want %q ", pre, c.key, got, c.want)
}
}
}
func TestRescope(t *testing.T) {
b := NewBuilder()
l := b.Create(map[uint64]any{10: "a", 39: "b"})
r := b.Create(map[uint64]any{10: "A", 39: "B", 75: "C"})
b.Rescope()
m := b.Intersect(l, r)
if got, want := m.String(), "{10: a, 39: b}"; got != want {
t.Errorf("Intersect(%s, %s) got %q. want %q", l, r, got, want)
}
if m.Scope() == l.Scope() {
t.Errorf("m.Scope() = %v should not equal l.Scope() = %v", m.Scope(), l.Scope())
}
if m.Scope() == r.Scope() {
t.Errorf("m.Scope() = %v should not equal r.Scope() = %v", m.Scope(), r.Scope())
}
}
func TestSharing(t *testing.T) {
b := NewBuilder()
l := b.Create(map[uint64]any{0: "a", 1: "b"})
r := b.Create(map[uint64]any{1: "B", 2: "C"})
rleftold := r.n.(*branch).left
m := b.Merge(l, r)
if mleft := m.n.(*branch).left; mleft != l.n {
t.Errorf("unexpected value for left branch of %v. want %v got %v", m, l, mleft)
}
if rleftnow := r.n.(*branch).left; rleftnow != rleftold {
t.Errorf("r.n.(*branch).left was modified by the Merge operation. was %v now %v", rleftold, rleftnow)
}
}
================================================
FILE: go/callgraph/vta/propagation.go
================================================
// Copyright 2021 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package vta
import (
"go/types"
"iter"
"slices"
"golang.org/x/tools/go/callgraph/vta/internal/trie"
"golang.org/x/tools/go/ssa"
"golang.org/x/tools/go/types/typeutil"
)
// scc computes strongly connected components (SCCs) of `g` using the
// classical Tarjan's algorithm for SCCs. The result is two slices:
// - sccs: the SCCs, each represented as a slice of node indices
// - idxToSccID: the inverse map, from node index to SCC number.
//
// The SCCs are sorted in reverse topological order: for SCCs
// with ids X and Y s.t. X < Y, Y comes before X in the topological order.
func scc(g *vtaGraph) (sccs [][]idx, idxToSccID []int) {
// standard data structures used by Tarjan's algorithm.
type state struct {
pre int // preorder of the node (0 if unvisited)
lowLink int
onStack bool
}
states := make([]state, g.numNodes())
var stack []idx
idxToSccID = make([]int, g.numNodes())
nextPre := 0
var doSCC func(idx)
doSCC = func(n idx) {
nextPre++
ns := &states[n]
*ns = state{pre: nextPre, lowLink: nextPre, onStack: true}
stack = append(stack, n)
for s := range g.successors(n) {
if ss := &states[s]; ss.pre == 0 {
// Analyze successor s that has not been visited yet.
doSCC(s)
ns.lowLink = min(ns.lowLink, ss.lowLink)
} else if ss.onStack {
// The successor is on the stack, meaning it has to be
// in the current SCC.
ns.lowLink = min(ns.lowLink, ss.pre)
}
}
// if n is a root node, pop the stack and generate a new SCC.
if ns.lowLink == ns.pre {
sccStart := slicesLastIndex(stack, n)
scc := slices.Clone(stack[sccStart:])
stack = stack[:sccStart]
sccID := len(sccs)
sccs = append(sccs, scc)
for _, w := range scc {
states[w].onStack = false
idxToSccID[w] = sccID
}
}
}
for n, nn := 0, g.numNodes(); n < nn; n++ {
if states[n].pre == 0 {
doSCC(idx(n))
}
}
return sccs, idxToSccID
}
// slicesLastIndex returns the index of the last occurrence of v in s, or -1 if v is
// not present in s.
//
// slicesLastIndex iterates backwards through the elements of s, stopping when the ==
// operator determines an element is equal to v.
func slicesLastIndex[S ~[]E, E comparable](s S, v E) int {
// TODO: move to / dedup with slices.LastIndex
for i := len(s) - 1; i >= 0; i-- {
if s[i] == v {
return i
}
}
return -1
}
// propType represents type information being propagated
// over the vta graph. f != nil only for function nodes
// and nodes reachable from function nodes. There, we also
// remember the actual *ssa.Function in order to more
// precisely model higher-order flow.
type propType struct {
typ types.Type
f *ssa.Function
}
// propTypeMap is an auxiliary structure that serves
// the role of a map from nodes to a set of propTypes.
type propTypeMap map[node]*trie.MutMap
// propTypes returns an iterator for the propTypes associated with
// node `n` in map `ptm`.
func (ptm propTypeMap) propTypes(n node) iter.Seq[propType] {
return func(yield func(propType) bool) {
if types := ptm[n]; types != nil {
types.M.Range(func(_ uint64, elem any) bool {
return yield(elem.(propType))
})
}
}
}
// propagate reduces the `graph` based on its SCCs and
// then propagates type information through the reduced
// graph. The result is a map from nodes to a set of types
// and functions, stemming from higher-order data flow,
// reaching the node. `canon` is used for type uniqueness.
func propagate(graph *vtaGraph, canon *typeutil.Map) propTypeMap {
sccs, idxToSccID := scc(graph)
// propTypeIds are used to create unique ids for
// propType, to be used for trie-based type sets.
propTypeIds := make(map[propType]uint64)
// Id creation is based on == equality, which works
// as types are canonicalized (see getPropType).
propTypeId := func(p propType) uint64 {
if id, ok := propTypeIds[p]; ok {
return id
}
id := uint64(len(propTypeIds))
propTypeIds[p] = id
return id
}
builder := trie.NewBuilder()
// Initialize sccToTypes to avoid repeated check
// for initialization later.
sccToTypes := make([]*trie.MutMap, len(sccs))
for sccID, scc := range sccs {
typeSet := builder.MutEmpty()
for _, idx := range scc {
if n := graph.node[idx]; hasInitialTypes(n) {
// add the propType for idx to typeSet.
pt := getPropType(n, canon)
typeSet.Update(propTypeId(pt), pt)
}
}
sccToTypes[sccID] = &typeSet
}
for i := len(sccs) - 1; i >= 0; i-- {
nextSccs := make(map[int]empty)
for _, n := range sccs[i] {
for succ := range graph.successors(n) {
nextSccs[idxToSccID[succ]] = empty{}
}
}
// Propagate types to all successor SCCs.
for nextScc := range nextSccs {
sccToTypes[nextScc].Merge(sccToTypes[i].M)
}
}
nodeToTypes := make(propTypeMap, graph.numNodes())
for sccID, scc := range sccs {
types := sccToTypes[sccID]
for _, idx := range scc {
nodeToTypes[graph.node[idx]] = types
}
}
return nodeToTypes
}
// hasInitialTypes check if a node can have initial types.
// Returns true iff `n` is not a panic, recover, nestedPtr*
// node, nor a node whose type is an interface.
func hasInitialTypes(n node) bool {
switch n.(type) {
case panicArg, recoverReturn, nestedPtrFunction, nestedPtrInterface:
return false
default:
return !types.IsInterface(n.Type())
}
}
// getPropType creates a propType for `node` based on its type.
// propType.typ is always node.Type(). If node is function, then
// propType.val is the underlying function; nil otherwise.
func getPropType(node node, canon *typeutil.Map) propType {
t := canonicalize(node.Type(), canon)
if fn, ok := node.(function); ok {
return propType{f: fn.f, typ: t}
}
return propType{f: nil, typ: t}
}
================================================
FILE: go/callgraph/vta/propagation_test.go
================================================
// Copyright 2021 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package vta
import (
"go/token"
"go/types"
"math"
"reflect"
"slices"
"sort"
"strings"
"testing"
"golang.org/x/tools/go/ssa"
"golang.org/x/tools/go/ssa/ssautil"
"golang.org/x/tools/internal/testfiles"
"golang.org/x/tools/txtar"
"golang.org/x/tools/go/types/typeutil"
)
// val is a test data structure for creating ssa.Value
// outside of the ssa package. Needed for manual creation
// of vta graph nodes in testing.
type val struct {
name string
typ types.Type
}
func (v val) String() string {
return v.name
}
func (v val) Name() string {
return v.name
}
func (v val) Type() types.Type {
return v.typ
}
func (v val) Parent() *ssa.Function {
return nil
}
func (v val) Referrers() *[]ssa.Instruction {
return nil
}
func (v val) Pos() token.Pos {
return token.NoPos
}
// newLocal creates a new local node with ssa.Value
// named `name` and type `t`.
func newLocal(name string, t types.Type) local {
return local{val: val{name: name, typ: t}}
}
// sccString is a utility for stringifying `nodeToScc`. Every
// scc is represented as a string where string representation
// of scc nodes are sorted and concatenated using `;`.
func sccString(sccs [][]idx, g *vtaGraph) []string {
var sccsStr []string
for _, scc := range sccs {
var nodesStr []string
for _, idx := range scc {
nodesStr = append(nodesStr, g.node[idx].String())
}
sort.Strings(nodesStr)
sccsStr = append(sccsStr, strings.Join(nodesStr, ";"))
}
return sccsStr
}
// nodeToTypeString is testing utility for stringifying results
// of type propagation: propTypeMap `pMap` is converted to a map
// from node strings to a string consisting of type stringifications
// concatenated with `;`. We stringify reachable type information
// that also has an accompanying function by the function name.
func nodeToTypeString(pMap propTypeMap) map[string]string {
// Convert propType to a string. If propType has
// an attached function, return the function name.
// Otherwise, return the type name.
propTypeString := func(p propType) string {
if p.f != nil {
return p.f.Name()
}
return p.typ.String()
}
nodeToTypeStr := make(map[string]string)
for node := range pMap {
var propStrings []string
for prop := range pMap.propTypes(node) {
s := propTypeString(prop)
s = strings.ReplaceAll(s, "example.com.", "")
propStrings = append(propStrings, s)
}
sort.Strings(propStrings)
nodeToTypeStr[node.String()] = strings.Join(propStrings, ";")
}
return nodeToTypeStr
}
// sccEqual compares two sets of SCC stringifications.
func sccEqual(sccs1 []string, sccs2 []string) bool {
if len(sccs1) != len(sccs2) {
return false
}
sort.Strings(sccs1)
sort.Strings(sccs2)
return reflect.DeepEqual(sccs1, sccs2)
}
// isRevTopSorted checks if sccs of `g` are sorted in reverse
// topological order:
//
// for every edge x -> y in g, nodeToScc[x] > nodeToScc[y]
func isRevTopSorted(g *vtaGraph, idxToScc []int) bool {
for n := range idxToScc {
for s := range g.successors(idx(n)) {
if idxToScc[n] < idxToScc[s] {
return false
}
}
}
return true
}
func sccMapsConsistent(sccs [][]idx, idxToSccID []int) bool {
for id, scc := range sccs {
for _, idx := range scc {
if idxToSccID[idx] != id {
return false
}
}
}
for i, id := range idxToSccID {
if !slices.Contains(sccs[id], idx(i)) {
return false
}
}
return true
}
// testSuite produces a named set of graphs as follows, where
// parentheses contain node types and F nodes stand for function
// nodes whose content is function named F:
//
// no-cycles:
// t0 (A) -> t1 (B) -> t2 (C)
//
// trivial-cycle:
// <-------- <--------
// | | | |
// t0 (A) -> t1 (B) ->
//
// circle-cycle:
// t0 (A) -> t1 (A) -> t2 (B)
// | |
// <--------------------
//
// fully-connected:
// t0 (A) <-> t1 (B)
// \ /
// t2(C)
//
// subsumed-scc:
// t0 (A) -> t1 (B) -> t2(B) -> t3 (A)
// | | | |
// | <--------- |
// <-----------------------------
//
// more-realistic:
// <--------
// | |
// t0 (A) -->
// ---------->
// | |
// t1 (A) -> t2 (B) -> F1 -> F2 -> F3 -> F4
// | | | |
// <------- <------------
func testSuite(t *testing.T) map[string]*vtaGraph {
ar := txtar.Parse([]byte(`-- go.mod --
module example.com
go 1.24
-- p.go --
package p
type A struct{}
type B struct{}
type C struct{}
func F1()
func F2()
func F3()
func F4()
`))
ppkgs := testfiles.LoadPackages(t, ar, ".")
if len(ppkgs) != 1 {
t.Fatalf("LoadPackages returned %d packages, want 1", len(ppkgs))
}
_, ssapkgs := ssautil.Packages(ppkgs, ssa.BuilderMode(0))
pkg := ssapkgs[0]
a := pkg.Type("A").Type().(*types.Named)
b := pkg.Type("B").Type().(*types.Named)
c := pkg.Type("C").Type().(*types.Named)
f1 := pkg.Func("F1")
f2 := pkg.Func("F2")
f3 := pkg.Func("F3")
f4 := pkg.Func("F4")
graphs := make(map[string]*vtaGraph)
v := &vtaGraph{}
graphs["no-cycles"] = v
v.addEdge(newLocal("t0", a), newLocal("t1", b))
v.addEdge(newLocal("t1", b), newLocal("t2", c))
v = &vtaGraph{}
graphs["trivial-cycle"] = v
v.addEdge(newLocal("t0", a), newLocal("t0", a))
v.addEdge(newLocal("t1", b), newLocal("t1", b))
v = &vtaGraph{}
graphs["circle-cycle"] = v
v.addEdge(newLocal("t0", a), newLocal("t1", a))
v.addEdge(newLocal("t1", a), newLocal("t2", b))
v.addEdge(newLocal("t2", b), newLocal("t0", a))
v = &vtaGraph{}
graphs["fully-connected"] = v
v.addEdge(newLocal("t0", a), newLocal("t1", b))
v.addEdge(newLocal("t0", a), newLocal("t2", c))
v.addEdge(newLocal("t1", b), newLocal("t0", a))
v.addEdge(newLocal("t1", b), newLocal("t2", c))
v.addEdge(newLocal("t2", c), newLocal("t0", a))
v.addEdge(newLocal("t2", c), newLocal("t1", b))
v = &vtaGraph{}
graphs["subsumed-scc"] = v
v.addEdge(newLocal("t0", a), newLocal("t1", b))
v.addEdge(newLocal("t1", b), newLocal("t2", b))
v.addEdge(newLocal("t2", b), newLocal("t1", b))
v.addEdge(newLocal("t2", b), newLocal("t3", a))
v.addEdge(newLocal("t3", a), newLocal("t0", a))
v = &vtaGraph{}
graphs["more-realistic"] = v
v.addEdge(newLocal("t0", a), newLocal("t0", a))
v.addEdge(newLocal("t1", a), newLocal("t2", b))
v.addEdge(newLocal("t2", b), newLocal("t1", a))
v.addEdge(newLocal("t2", b), function{f1})
v.addEdge(function{f1}, function{f2})
v.addEdge(function{f1}, function{f3})
v.addEdge(function{f2}, function{f3})
v.addEdge(function{f3}, function{f1})
v.addEdge(function{f3}, function{f4})
return graphs
}
func TestSCC(t *testing.T) {
suite := testSuite(t)
for _, test := range []struct {
name string
graph *vtaGraph
want []string
}{
// No cycles results in three separate SCCs: {t0} {t1} {t2}
{name: "no-cycles", graph: suite["no-cycles"], want: []string{"Local(t0)", "Local(t1)", "Local(t2)"}},
// The two trivial self-loop cycles results in: {t0} {t1}
{name: "trivial-cycle", graph: suite["trivial-cycle"], want: []string{"Local(t0)", "Local(t1)"}},
// The circle cycle produce a single SCC: {t0, t1, t2}
{name: "circle-cycle", graph: suite["circle-cycle"], want: []string{"Local(t0);Local(t1);Local(t2)"}},
// Similar holds for fully connected SCC: {t0, t1, t2}
{name: "fully-connected", graph: suite["fully-connected"], want: []string{"Local(t0);Local(t1);Local(t2)"}},
// Subsumed SCC also has a single SCC: {t0, t1, t2, t3}
{name: "subsumed-scc", graph: suite["subsumed-scc"], want: []string{"Local(t0);Local(t1);Local(t2);Local(t3)"}},
// The more realistic example has the following SCCs: {t0} {t1, t2} {F1, F2, F3} {F4}
{name: "more-realistic", graph: suite["more-realistic"], want: []string{"Local(t0)", "Local(t1);Local(t2)", "Function(F1);Function(F2);Function(F3)", "Function(F4)"}},
} {
sccs, idxToSccID := scc(test.graph)
if got := sccString(sccs, test.graph); !sccEqual(test.want, got) {
t.Errorf("want %v for graph %v; got %v", test.want, test.name, got)
}
if !isRevTopSorted(test.graph, idxToSccID) {
t.Errorf("%v not topologically sorted", test.name)
}
if !sccMapsConsistent(sccs, idxToSccID) {
t.Errorf("%v: scc maps not consistent", test.name)
}
break
}
}
func TestPropagation(t *testing.T) {
suite := testSuite(t)
var canon typeutil.Map
for _, test := range []struct {
name string
graph *vtaGraph
want map[string]string
}{
// No cycles graph pushes type information forward.
{name: "no-cycles", graph: suite["no-cycles"],
want: map[string]string{
"Local(t0)": "A",
"Local(t1)": "A;B",
"Local(t2)": "A;B;C",
},
},
// No interesting type flow in trivial cycle graph.
{name: "trivial-cycle", graph: suite["trivial-cycle"],
want: map[string]string{
"Local(t0)": "A",
"Local(t1)": "B",
},
},
// Circle cycle makes type A and B get propagated everywhere.
{name: "circle-cycle", graph: suite["circle-cycle"],
want: map[string]string{
"Local(t0)": "A;B",
"Local(t1)": "A;B",
"Local(t2)": "A;B",
},
},
// Similarly for fully connected graph.
{name: "fully-connected", graph: suite["fully-connected"],
want: map[string]string{
"Local(t0)": "A;B;C",
"Local(t1)": "A;B;C",
"Local(t2)": "A;B;C",
},
},
// The outer loop of subsumed-scc pushes A and B through the graph.
{name: "subsumed-scc", graph: suite["subsumed-scc"],
want: map[string]string{
"Local(t0)": "A;B",
"Local(t1)": "A;B",
"Local(t2)": "A;B",
"Local(t3)": "A;B",
},
},
// More realistic graph has a more fine grained flow.
{name: "more-realistic", graph: suite["more-realistic"],
want: map[string]string{
"Local(t0)": "A",
"Local(t1)": "A;B",
"Local(t2)": "A;B",
"Function(F1)": "A;B;F1;F2;F3",
"Function(F2)": "A;B;F1;F2;F3",
"Function(F3)": "A;B;F1;F2;F3",
"Function(F4)": "A;B;F1;F2;F3;F4",
},
},
} {
if got := nodeToTypeString(propagate(test.graph, &canon)); !reflect.DeepEqual(got, test.want) {
t.Errorf("want %v for graph %v; got %v", test.want, test.name, got)
}
}
}
func testLastIndex[S ~[]E, E comparable](t *testing.T, s S, e E, want int) {
if got := slicesLastIndex(s, e); got != want {
t.Errorf("LastIndex(%v, %v): got %v want %v", s, e, got, want)
}
}
func TestLastIndex(t *testing.T) {
testLastIndex(t, []int{10, 20, 30}, 10, 0)
testLastIndex(t, []int{10, 20, 30}, 20, 1)
testLastIndex(t, []int{10, 20, 30}, 30, 2)
testLastIndex(t, []int{10, 20, 30}, 42, -1)
testLastIndex(t, []int{10, 20, 10}, 10, 2)
testLastIndex(t, []int{20, 10, 10}, 10, 2)
testLastIndex(t, []int{10, 10, 20}, 10, 1)
type foo struct {
i int
s string
}
testLastIndex(t, []foo{{1, "abc"}, {2, "abc"}, {1, "xyz"}}, foo{1, "abc"}, 0)
// Test that LastIndex doesn't use bitwise comparisons for floats.
neg0 := 1 / math.Inf(-1)
nan := math.NaN()
testLastIndex(t, []float64{0, neg0}, 0, 1)
testLastIndex(t, []float64{0, neg0}, neg0, 1)
testLastIndex(t, []float64{neg0, 0}, 0, 1)
testLastIndex(t, []float64{neg0, 0}, neg0, 1)
testLastIndex(t, []float64{0, nan}, 0, 0)
testLastIndex(t, []float64{0, nan}, nan, -1)
testLastIndex(t, []float64{0, nan}, 1, -1)
}
================================================
FILE: go/callgraph/vta/testdata/src/arrays_generics.go
================================================
// Copyright 2023 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// go:build ignore
package testdata
type F func()
func set[T [1]F | [2]F](arr *T, i int) {
// Indexes into a pointer to an indexable type T and T does not have a coretype.
// SSA instruction: t0 = &arr[i]
(*arr)[i] = bar
}
func bar() {
print("here")
}
func Foo() {
var arr [1]F
set(&arr, 0)
arr[0]()
}
// WANT:
// Foo: set[[1]testdata.F](t0, 0:int) -> set[[1]testdata.F]; t3() -> bar
================================================
FILE: go/callgraph/vta/testdata/src/callgraph_collections.go
================================================
// Copyright 2021 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// go:build ignore
package testdata
type I interface {
Foo()
}
type A struct{}
func (a A) Foo() {}
type B struct{}
func (b B) Foo() {}
func Do(a A, b B) map[I]I {
m := make(map[I]I)
m[a] = B{}
m[b] = b
return m
}
func Baz(a A, b B) {
var x []I
for k, v := range Do(a, b) {
k.Foo()
v.Foo()
x = append(x, k)
}
x[len(x)-1].Foo()
}
// WANT:
// Baz: Do(a, b) -> Do; invoke t16.Foo() -> A.Foo, B.Foo; invoke t5.Foo() -> A.Foo, B.Foo; invoke t6.Foo() -> B.Foo
================================================
FILE: go/callgraph/vta/testdata/src/callgraph_comma_maps.go
================================================
// Copyright 2023 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// go:build ignore
package testdata
type I interface {
Name() string
Foo()
}
var is = make(map[string]I)
func init() {
register(A{})
register(B{})
}
func register(i I) {
is[i.Name()] = i
}
type A struct{}
func (a A) Foo() {}
func (a A) Name() string { return "a" }
type B struct{}
func (b B) Foo() {}
func (b B) Name() string { return "b" }
func Do(n string) {
i, ok := is[n]
if !ok {
return
}
i.Foo()
}
func Go(n string) {
if i, ok := is[n]; !ok {
return
} else {
i.Foo()
}
}
func To(n string) {
var i I
var ok bool
if i, ok = is[n]; !ok {
return
}
i.Foo()
}
func Ro(n string) {
i := is[n]
i.Foo()
}
// Relevant SSA:
// func Do(n string):
// t0 = *is
// t1 = t0[n],ok
// t2 = extract t1 #0
// t3 = extract t1 #1
// if t3 goto 2 else 1
// 1:
// return
// 2:
// t4 = invoke t2.Foo()
// return
// WANT:
// register: invoke i.Name() -> A.Name, B.Name
// Do: invoke t2.Foo() -> A.Foo, B.Foo
// Go: invoke t2.Foo() -> A.Foo, B.Foo
// To: invoke t2.Foo() -> A.Foo, B.Foo
// Ro: invoke t1.Foo() -> A.Foo, B.Foo
================================================
FILE: go/callgraph/vta/testdata/src/callgraph_field_funcs.go
================================================
// Copyright 2021 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// go:build ignore
package testdata
type WrappedFunc struct {
F func() complex64
}
func callWrappedFunc(f WrappedFunc) {
f.F()
}
func foo() complex64 {
println("foo")
return -1
}
func Foo(b bool) {
callWrappedFunc(WrappedFunc{foo})
x := func() {}
y := func() {}
var a *func()
if b {
a = &x
} else {
a = &y
}
(*a)()
}
// Relevant SSA:
// func Foo(b bool):
// t0 = local WrappedFunc (complit)
// t1 = &t0.F [#0]
// *t1 = foo
// t2 = *t0
// t3 = callWrappedFunc(t2)
// t4 = new func() (x)
// *t4 = Foo$1
// t5 = new func() (y)
// *t5 = Foo$2
// if b goto 1 else 3
// 1:
// jump 2
// 2:
// t6 = phi [1: t4, 3: t5] #a
// t7 = *t6
// t8 = t7()
// return
// 3:
// jump 2
//
// func callWrappedFunc(f WrappedFunc):
// t0 = local WrappedFunc (f)
// *t0 = f
// t1 = &t0.F [#0]
// t2 = *t1
// t3 = t2()
// return
// WANT:
// callWrappedFunc: t2() -> foo
// Foo: callWrappedFunc(t2) -> callWrappedFunc; t7() -> Foo$1, Foo$2
================================================
FILE: go/callgraph/vta/testdata/src/callgraph_fields.go
================================================
// Copyright 2021 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// go:build ignore
package testdata
type I interface {
Foo()
}
type A struct {
I
}
func (a *A) Do() {
a.Foo()
}
type B struct{}
func (b B) Foo() {}
func NewA(b B) *A {
return &A{I: &b}
}
func Baz(b B) {
a := NewA(b)
a.Do()
}
// WANT:
// Baz: (*A).Do(t0) -> A.Do; NewA(b) -> NewA
// A.Do: invoke t1.Foo() -> B.Foo
================================================
FILE: go/callgraph/vta/testdata/src/callgraph_generics.go
================================================
// Copyright 2021 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// go:build ignore
package testdata
func instantiated[X any](x *X) int {
print(x)
return 0
}
type I interface {
Bar()
}
func interfaceInstantiated[X I](x X) {
x.Bar()
}
type A struct{}
func (a A) Bar() {}
type B struct{}
func (b B) Bar() {}
func Foo(a A, b B) {
x := true
instantiated[bool](&x)
y := 1
instantiated[int](&y)
interfaceInstantiated[A](a)
interfaceInstantiated[B](b)
}
// Relevant SSA:
//func Foo(a A, b B):
// t0 = new bool (x)
// *t0 = true:bool
// t1 = instantiated[bool](t2)
// t1 = new int (y)
// *t2 = 1:int
// t3 = instantiated[[int]](t4)
// t4 = interfaceInstantiated[testdata.A](a)
// t5 = interfaceInstantiated[testdata.B](b)
// return
//
//func interfaceInstantiated[[testdata.B]](x B):
// t0 = (B).Bar(b)
// return
//
//func interfaceInstantiated[X I](x X):
// (external)
// WANT:
// Foo: instantiated[bool](t0) -> instantiated[bool]; instantiated[int](t2) -> instantiated[int]; interfaceInstantiated[testdata.A](a) -> interfaceInstantiated[testdata.A]; interfaceInstantiated[testdata.B](b) -> interfaceInstantiated[testdata.B]
// interfaceInstantiated[testdata.B]: (B).Bar(x) -> B.Bar
// interfaceInstantiated[testdata.A]: (A).Bar(x) -> A.Bar
================================================
FILE: go/callgraph/vta/testdata/src/callgraph_ho.go
================================================
// Copyright 2021 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// go:build ignore
package testdata
func Foo() {}
func Do(b bool) func() {
if b {
return Foo
}
return func() {}
}
func Finish(h func()) {
h()
}
func Baz(b bool) {
Finish(Do(b))
}
// Relevant SSA:
// func Baz(b bool):
// t0 = Do(b)
// t1 = Finish(t0)
// return
// func Do(b bool) func():
// if b goto 1 else 2
// 1:
// return Foo
// 2:
// return Do$1
// func Finish(h func()):
// t0 = h()
// return
// WANT:
// Baz: Do(b) -> Do; Finish(t0) -> Finish
// Finish: h() -> Do$1, Foo
================================================
FILE: go/callgraph/vta/testdata/src/callgraph_interfaces.go
================================================
// Copyright 2021 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// go:build ignore
package testdata
type I interface {
Foo()
}
type A struct{}
func (a A) Foo() {}
type B struct{}
func (b B) Foo() {}
type C struct{}
func (c C) Foo() {}
func NewB() B {
return B{}
}
func Do(b bool) I {
if b {
return A{}
}
c := C{}
c.Foo()
return NewB()
}
func Baz(b bool) {
Do(b).Foo()
}
// Relevant SSA:
// func Baz(b bool):
// t0 = Do(b)
// t1 = invoke t0.Foo()
// return
// func Do(b bool) I:
// ...
// t1 = (C).Foo(C{}:C)
// t2 = NewB()
// t3 = make I <- B (t2)
// return t3
// WANT:
// Baz: Do(b) -> Do; invoke t0.Foo() -> A.Foo, B.Foo
// Do: (C).Foo(C{}:C) -> C.Foo; NewB() -> NewB
================================================
FILE: go/callgraph/vta/testdata/src/callgraph_issue_57756.go
================================================
// Copyright 2023 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// go:build ignore
package testdata
// Test that the values of a named function type are correctly
// flowing from interface objects i in i.Foo() to the receiver
// parameters of callees of i.Foo().
type H func()
func (h H) Do() {
h()
}
type I interface {
Do()
}
func Bar() I {
return H(func() {})
}
func For(g G) {
b := Bar()
b.Do()
g[0] = b
g.Goo()
}
type G []I
func (g G) Goo() {
g[0].Do()
}
// Relevant SSA:
// func Bar$1():
// return
//
// func Bar() I:
// t0 = changetype H <- func() (Bar$1)
// t1 = make I <- H (t0)
//
// func For():
// t0 = Bar()
// t1 = invoke t0.Do()
// t2 = &g[0:int]
// *t2 = t0
// t3 = (G).Goo(g)
//
// func (h H) Do():
// t0 = h()
//
// func (g G) Goo():
// t0 = &g[0:int]
// t1 = *t0
// t2 = invoke t1.Do()
// WANT:
// For: (G).Goo(g) -> G.Goo; Bar() -> Bar; invoke t0.Do() -> H.Do
// H.Do: h() -> Bar$1
// G.Goo: invoke t1.Do() -> H.Do
================================================
FILE: go/callgraph/vta/testdata/src/callgraph_nested_ptr.go
================================================
// Copyright 2021 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// go:build ignore
package testdata
type I interface {
Foo()
}
type A struct{}
func (a A) Foo() {}
type B struct{}
func (b B) Foo() {}
func Do(i **I) {
**i = A{}
}
func Bar(i **I) {
**i = B{}
}
func Baz(i **I) {
Do(i)
(**i).Foo()
}
// Relevant SSA:
// func Baz(i **I):
// t0 = Do(i)
// t1 = *i
// t2 = *t1
// t3 = invoke t2.Foo()
// return
// func Bar(i **I):
// t0 = *i
// t1 = local B (complit)
// t2 = *t1
// t3 = make I <- B (t2)
// *t0 = t3
// return
// func Do(i **I):
// t0 = *i
// t1 = local A (complit)
// t2 = *t1
// t3 = make I <- A (t2)
// *t0 = t3
// return
// WANT:
// Baz: Do(i) -> Do; invoke t2.Foo() -> A.Foo, B.Foo
================================================
FILE: go/callgraph/vta/testdata/src/callgraph_pointers.go
================================================
// Copyright 2021 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// go:build ignore
package testdata
type I interface {
Foo()
}
type A struct {
f *I
}
func (a A) Foo() {}
type B struct{}
func (b B) Foo() {}
func Do(a A, i I, c bool) *I {
if c {
*a.f = a
} else {
a.f = &i
}
(*a.f).Foo()
return &i
}
func Baz(a A, b B, c bool) {
x := Do(a, b, c)
(*x).Foo()
}
// The command a.f = &i introduces aliasing that results in
// A and B reaching both *A.f and return value of Do(a, b, c).
// WANT:
// Baz: Do(a, t0, c) -> Do; invoke t2.Foo() -> A.Foo, B.Foo
// Do: invoke t8.Foo() -> A.Foo, B.Foo
================================================
FILE: go/callgraph/vta/testdata/src/callgraph_range_over_func.go
================================================
// Copyright 2024 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// go:build ignore
package testdata
type I interface {
Foo()
}
type A struct{}
func (a A) Foo() {}
type B struct{}
func (b B) Foo() {}
type C struct{}
func (c C) Foo() {} // Test that this is not called.
type iset []I
func (i iset) All() func(func(I) bool) {
return func(yield func(I) bool) {
for _, v := range i {
if !yield(v) {
return
}
}
}
}
var x = iset([]I{A{}, B{}})
func X() {
for i := range x.All() {
i.Foo()
}
}
func Y() I {
for i := range x.All() {
return i
}
return nil
}
func Bar() {
X()
y := Y()
y.Foo()
}
// Relevant SSA:
//func X$1(I) bool:
// t0 = *jump$1
// t1 = t0 == 0:int
// if t1 goto 1 else 2
//1:
// *jump$1 = -1:int
// t2 = invoke arg0.Foo()
// *jump$1 = 0:int
// return true:bool
//2:
// t3 = make interface{} <- string ("yield function ca...":string) interface{}
// panic t3
//
//func All$1(yield func(I) bool):
// t0 = *i
// t1 = len(t0)
// jump 1
//1:
// t2 = phi [0: -1:int, 2: t3] #rangeindex
// t3 = t2 + 1:int
// t4 = t3 < t1
// if t4 goto 2 else 3
//2:
// t5 = &t0[t3]
// t6 = *t5
// t7 = yield(t6)
// if t7 goto 1 else 4
//
//func Bar():
// t0 = X()
// t1 = Y()
// t2 = invoke t1.Foo()
// return
// WANT:
// Bar: X() -> X; Y() -> Y; invoke t1.Foo() -> A.Foo, B.Foo
// X$1: invoke arg0.Foo() -> A.Foo, B.Foo
// All$1: yield(t6) -> X$1, Y$1
================================================
FILE: go/callgraph/vta/testdata/src/callgraph_recursive_types.go
================================================
// Copyright 2021 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// go:build ignore
package testdata
type I interface {
Foo() I
}
type A struct {
i int
a *A
}
func (a *A) Foo() I {
return a
}
type B **B
type C *D
type D *C
func Bar(a *A, b *B, c *C, d *D) {
Baz(a)
Baz(a.a)
sink(*b)
sink(*c)
sink(*d)
}
func Baz(i I) {
i.Foo()
}
func sink(i interface{}) {
print(i)
}
// Relevant SSA:
// func Baz(i I):
// t0 = invoke i.Foo()
// return
//
// func Bar(a *A, b *B):
// t0 = make I <- *A (a)
// t1 = Baz(t0)
// ...
// WANT:
// Bar: Baz(t0) -> Baz; Baz(t4) -> Baz; sink(t10) -> sink; sink(t13) -> sink; sink(t7) -> sink
// Baz: invoke i.Foo() -> A.Foo
================================================
FILE: go/callgraph/vta/testdata/src/callgraph_static.go
================================================
// Copyright 2021 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// go:build ignore
package testdata
type A struct{}
func (a A) foo() {}
func Bar() {}
func Baz(a A) {
a.foo()
Bar()
Baz(A{})
}
// Relevant SSA:
// func Baz(a A):
// t0 = (A).foo(a)
// t1 = Bar()
// t2 = Baz(A{}:A)
// WANT:
// Baz: (A).foo(a) -> A.foo; Bar() -> Bar; Baz(A{}:A) -> Baz
================================================
FILE: go/callgraph/vta/testdata/src/callgraph_type_aliases.go
================================================
// Copyright 2024 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// go:build ignore
// This file is the same as callgraph_interfaces.go except for
// types J, X, Y, and Z aliasing types I, A, B, and C, resp.
package testdata
type I interface {
Foo()
}
type A struct{}
func (a A) Foo() {}
type B struct{}
func (b B) Foo() {}
type C struct{}
func (c C) Foo() {}
type J = I
type X = A
type Y = B
type Z = C
func NewY() Y {
return Y{}
}
func Do(b bool) J {
if b {
return X{}
}
z := Z{}
z.Foo()
return NewY()
}
func Baz(b bool) {
Do(b).Foo()
}
// Relevant SSA:
// func Baz(b bool):
// t0 = Do(b)
// t1 = invoke t0.Foo()
// return
// func Do(b bool) I:
// ...
// t1 = (C).Foo(Z{}:Z)
// t2 = NewY()
// t3 = make I <- B (t2)
// return t3
// WANT:
// Baz: Do(b) -> Do; invoke t0.Foo() -> A.Foo, B.Foo
// Do: (C).Foo(Z{}:Z) -> C.Foo; NewY() -> NewY
================================================
FILE: go/callgraph/vta/testdata/src/channels.go
================================================
// Copyright 2021 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// go:build ignore
package testdata
func foo(c chan interface{}, j int) {
c <- j + 1
}
func Baz(i int) {
c := make(chan interface{})
go foo(c, i)
x := <-c
print(x)
}
// Relevant SSA:
// func foo(c chan interface{}, j int):
// t0 = j + 1:int
// t1 = make interface{} <- int (t0)
// send c <- t1 // t1 -> chan {}interface
// return
//
// func Baz(i int):
// t0 = make chan interface{} 0:int
// go foo(t0, i)
// t1 = <-t0 // chan {}interface -> t1
// t2 = print(t1)
// return
// WANT:
// Channel(chan interface{}) -> Local(t1)
// Local(t1) -> Channel(chan interface{})
================================================
FILE: go/callgraph/vta/testdata/src/closures.go
================================================
// Copyright 2021 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// go:build ignore
package testdata
type I interface {
Foo()
}
func Do(i I) { i.Foo() }
func Baz(b bool, h func(I)) {
var i I
a := func(g func(I)) {
g(i)
}
if b {
h = Do
}
a(h)
}
// Relevant SSA:
// func Baz(b bool, h func(I)):
// t0 = new I (i)
// t1 = make closure Baz$1 [t0]
// if b goto 1 else 2
// 1:
// jump 2
// 2:
// t2 = phi [0: h, 1: Do] #h
// t3 = t1(t2)
// return
//
// func Baz$1(g func(I)):
// t0 = *i
// t1 = g(t0)
// return
// In the edge set Local(i) -> Local(t0), Local(t0) below,
// two occurrences of t0 come from t0 in Baz and Baz$1.
// WANT:
// Function(Do) -> Local(t2)
// Function(Baz$1) -> Local(t1)
// Local(h) -> Local(t2)
// Local(t0) -> Local(i)
// Local(i) -> Local(t0), Local(t0)
================================================
FILE: go/callgraph/vta/testdata/src/d/d.go
================================================
package d
func D(i int) int {
return i + 1
}
type Data struct {
V int
}
func (d Data) Do() int {
return d.V - 1
}
================================================
FILE: go/callgraph/vta/testdata/src/dynamic_calls.go
================================================
// Copyright 2021 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// go:build ignore
package testdata
type I interface {
foo(I)
}
type A struct{}
func (a A) foo(ai I) {}
type B struct{}
func (b B) foo(bi I) {}
func doWork() I { return nil }
func close() I { return nil }
func Baz(x B, h func() I, i I) I {
i.foo(x)
return h()
}
var g *B = &B{} // ensure *B.foo is created.
// Relevant SSA:
// func Baz(x B, h func() I, i I) I:
// t0 = make I <- B (x)
// t1 = invoke i.foo(t0)
// t2 = h()
// return t2
// Local(t0) has seemingly duplicates of successors. This
// happens in stringification of type propagation graph.
// Due to CHA, we analyze A.foo and *A.foo as well as B.foo
// and *B.foo, which have similar bodies and hence similar
// type flow that gets merged together during stringification.
// WANT:
// Return(doWork[0]) -> Local(t2)
// Return(close[0]) -> Local(t2)
// Local(t0) -> Local(ai), Local(ai), Local(bi), Local(bi)
// Constant(testdata.I) -> Return(close[0]), Return(doWork[0])
// Local(x) -> Local(t0)
================================================
FILE: go/callgraph/vta/testdata/src/fields.go
================================================
// Copyright 2021 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// go:build ignore
package testdata
type I interface {
Foo()
}
type J interface {
I
Bar()
}
type A struct{}
func (a A) Foo() {}
func (a A) Bar() {}
type B struct {
a A
i I
}
func Do() B {
b := B{}
return b
}
func Baz(b B) {
var j J
j = b.a
j.Bar()
b.i = j
Do().i.Foo()
}
// Relevant SSA:
// func Baz(b B):
// t0 = local B (b)
// *t0 = b
// t1 = &t0.a [#0] // no flow here since a is of concrete type
// t2 = *t1
// t3 = make J <- A (t2)
// t4 = invoke t3.Bar()
// t5 = &t0.i [#1]
// t6 = change interface I <- J (t3)
// *t5 = t6
// t7 = Do()
// t8 = t7.i [#0]
// t9 = (A).Foo(t8)
// return
// WANT:
// Field(testdata.B:i) -> Local(t5), Local(t8)
// Local(t5) -> Field(testdata.B:i)
// Local(t2) -> Local(t3)
// Local(t3) -> Local(t6)
// Local(t6) -> Local(t5)
================================================
FILE: go/callgraph/vta/testdata/src/function_alias.go
================================================
// Copyright 2021 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// go:build ignore
package testdata
type Doer func()
type A struct {
foo func()
do Doer
}
func Baz(f func()) {
j := &f
k := &j
**k = func() {}
a := A{}
a.foo = **k
a.foo()
a.do = a.foo
a.do()
}
// Relevant SSA:
// func Baz(f func()):
// t0 = new func() (f)
// *t0 = f
// t1 = new *func() (j)
// *t1 = t0
// t2 = *t1
// *t2 = Baz$1
// t3 = local A (a)
// t4 = *t1
// t5 = *t4
// t6 = &t3.foo [#0]
// *t6 = t5
// t7 = &t3.foo [#0]
// t8 = *t7
// t9 = t8()
// t10 = &t3.foo [#0] *func()
// t11 = *t10 func()
// t12 = &t3.do [#1] *Doer
// t13 = changetype Doer <- func() (t11) Doer
// *t12 = t13
// t14 = &t3.do [#1] *Doer
// t15 = *t14 Doer
// t16 = t15() ()
// Flow chain showing that Baz$1 reaches t8():
// Baz$1 -> t2 <-> PtrFunction(func()) <-> t4 -> t5 -> t6 <-> Field(testdata.A:foo) <-> t7 -> t8
// Flow chain showing that Baz$1 reaches t15():
// Field(testdata.A:foo) <-> t10 -> t11 -> t13 -> t12 <-> Field(testdata.A:do) <-> t14 -> t15
// WANT:
// Local(f) -> Local(t0)
// Local(t0) -> PtrFunction(func())
// Function(Baz$1) -> Local(t2)
// PtrFunction(func()) -> Local(t0), Local(t2), Local(t4)
// Local(t2) -> PtrFunction(func())
// Local(t6) -> Field(testdata.A:foo)
// Local(t4) -> Local(t5), PtrFunction(func())
// Local(t5) -> Local(t6)
// Local(t7) -> Field(testdata.A:foo), Local(t8)
// Field(testdata.A:foo) -> Local(t10), Local(t6), Local(t7)
// Local(t6) -> Field(testdata.A:foo)
// Field(testdata.A:do) -> Local(t12), Local(t14)
// Local(t12) -> Field(testdata.A:do)
// Local(t10) -> Field(testdata.A:foo), Local(t11)
// Local(t11) -> Local(t13)
// Local(t13) -> Local(t12)
// Local(t14) -> Field(testdata.A:do), Local(t15)
================================================
FILE: go/callgraph/vta/testdata/src/generic_channels.go
================================================
// Copyright 2021 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// go:build ignore
package testdata
type I1 interface{}
type I2 interface{}
type I3 interface{}
func Foo[C interface{ ~chan I1 | ~chan<- I1 }](c C, j int) {
c <- j
}
func Bar[C interface{ ~chan I2 | ~<-chan I2 }](c C) {
x := <-c
print(x)
}
func Baz[C interface{ ~chan I3 | ~<-chan I3 }](c C) {
select {
case x := <-c:
print(x)
default:
}
}
// WANT:
// Local(t0) -> Channel(chan testdata.I1)
// Channel(chan testdata.I2) -> Local(t0)
// Channel(chan testdata.I3) -> Local(t0[2])
================================================
FILE: go/callgraph/vta/testdata/src/go117.go
================================================
// Copyright 2021 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// go:build ignore
package testdata
type J interface {
Foo()
Bar()
}
type B struct {
p int
}
func (b B) Foo() {}
func (b B) Bar() {}
func Wobble(b *B, s []J) {
x := (*[3]J)(s)
x[1] = b
a := &s[2]
(*a).Bar()
}
// Relevant SSA:
// func Wobble(b *B, s []J):
// t0 = slice to array pointer *[3]J <- []J (s) *[3]J
// t1 = &t0[1:int] *J
// t2 = make J <- *B (b) J
// *t1 = t2
// t3 = &s[2:int] *J
// ...
// WANT:
// Local(t1) -> Slice([]testdata.J)
// Slice([]testdata.J) -> Local(t1), Local(t3)
================================================
FILE: go/callgraph/vta/testdata/src/issue63146.go
================================================
package test
type embedded struct{}
type S struct{ embedded }
func (_ S) M() {}
type C interface {
M()
S
}
func G[T C]() {
t := T{embedded{}}
t.M()
}
func F() {
G[S]()
}
// WANT:
// F: G[testdata.S]() -> G[testdata.S]
// G[testdata.S]: (S).M(t2) -> S.M
// S.M: (testdata.S).M(t1) -> S.M
================================================
FILE: go/callgraph/vta/testdata/src/maps.go
================================================
// Copyright 2021 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// go:build ignore
package testdata
type I interface {
Foo() string
}
type J interface {
Foo() string
Bar()
}
type B struct {
p string
}
func (b B) Foo() string { return b.p }
func (b B) Bar() {}
func Baz(m map[I]I, b1, b2 B, n map[string]*J) *J {
m[b1] = b2
return n[b1.Foo()]
}
// Relevant SSA:
// func Baz(m map[I]I, b1 B, b2 B, n map[string]*J) *J:
// t0 = make I <- B (b1)
// t1 = make I <- B (b2)
// m[t0] = t1
// t2 = (B).Foo(b1)
// t3 = n[t2]
// return t3
// WANT:
// Local(b2) -> Local(t1)
// Local(t1) -> MapValue(testdata.I)
// Local(t0) -> MapKey(testdata.I)
// Local(t3) -> MapValue(*testdata.J), Return(Baz[0])
// MapValue(*testdata.J) -> Local(t3)
================================================
FILE: go/callgraph/vta/testdata/src/node_uniqueness.go
================================================
// Copyright 2021 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// go:build ignore
package testdata
// TestNodeTypeUniqueness checks if semantically equivalent types
// are being represented using the same pointer value in vta nodes.
// If not, some edges become missing in the string representation
// of the graph.
type I interface {
Foo()
}
type A struct{}
func (a A) Foo() {}
func Baz(a *A) (I, I, interface{}, interface{}) {
var i I
i = a
var ii I
aa := &A{}
ii = aa
m := make(map[int]int)
var iii interface{}
iii = m
var iiii interface{}
iiii = m
return i, ii, iii, iiii
}
// Relevant SSA:
// func Baz(a *A) (I, I, interface{}, interface{}):
// t0 = make I <- *A (a)
// t1 = new A (complit)
// t2 = make I <- *A (t1)
// t3 = make map[int]int
// t4 = make interface{} <- map[int]int (t3)
// t5 = make interface{} <- map[int]int (t3)
// return t0, t2, t4, t5
// Without canon approach, one of Pointer(*A) -> Local(t0) and Pointer(*A) -> Local(t2) edges is
// missing in the graph string representation. The original graph has both of the edges but the
// source node Pointer(*A) is not the same; two occurrences of Pointer(*A) are considered separate
// nodes. Since they have the same string representation, one edge gets overridden by the other
// during the graph stringification, instead of being joined together as in below.
// WANT:
// Pointer(*testdata.A) -> Local(t0), Local(t2)
// Local(t3) -> Local(t4), Local(t5)
================================================
FILE: go/callgraph/vta/testdata/src/panic.go
================================================
// Copyright 2021 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// go:build ignore
package testdata
type I interface {
foo()
}
type A struct{}
func (a A) foo() {}
func recover1() {
print("only this recover should execute")
if r, ok := recover().(I); ok {
r.foo()
}
}
func recover2() {
recover()
}
func Baz(a A) {
defer recover1()
defer recover()
panic(a)
}
// Relevant SSA:
// func recover1():
// t0 = print("only this recover...":string)
// t1 = recover()
// t2 = typeassert,ok t1.(I)
// t3 = extract t2 #0
// t4 = extract t2 #1
// if t4 goto 1 else 2
// 1:
// t5 = invoke t3.foo()
// jump 2
// 2:
// return
//
// func recover2():
// t0 = recover()
// return
//
// func Baz(i I):
// defer recover1()
// t0 = make interface{} <- A (a)
// panic t2
// t0 argument to panic in Baz gets ultimately connected to recover
// registers t1 in recover1() and t0 in recover2().
// WANT:
// Panic -> Recover
// Local(t0) -> Panic
// Recover -> Local(t0), Local(t1)
================================================
FILE: go/callgraph/vta/testdata/src/phi.go
================================================
// Copyright 2021 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// go:build ignore
package testdata
type A struct{}
type B struct{}
type I interface{ foo() }
func (a A) foo() {}
func (b B) foo() {}
func Baz(b B, c bool) {
var i I
if c {
i = b
} else {
a := A{}
i = a
}
i.foo()
}
// Relevant SSA:
// func Baz(b B, c bool):
// 0:
// if c goto 1 else 3
//
// 1:
// t0 = make I <- B (b)
// jump 2
//
// 2:
// t1 = phi [1: t0, 3: t3] #i
// t2 = invoke t1.foo()
// return
//
// 3:
// t3 = make I <- A (struct{}{}:A)
// jump 2
// WANT:
// Local(b) -> Local(t0)
// Local(t0) -> Local(t1)
// Local(t3) -> Local(t1)
// Constant(testdata.A) -> Local(t3)
================================================
FILE: go/callgraph/vta/testdata/src/phi_alias.go
================================================
// Copyright 2021 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// go:build ignore
package testdata
type I interface {
Foo()
}
type B struct {
p int
}
func (b B) Foo() {}
func Baz(i, j *I, b, c bool) {
if b {
i = j
}
*i = B{9}
if c {
(*i).Foo()
} else {
(*j).Foo()
}
}
// Relevant SSA:
// func Baz(i *I, j *I, b bool, c bool):
// if b goto 1 else 2
// 1:
// jump 2
// 2:
// t0 = phi [0: i, 1: j] #i
// t1 = local B (complit)
// t2 = &t1.p [#0]
// *t2 = 9:int
// t3 = *t1
// t4 = make I <- B (t3)
// *t0 = t4
// if c goto 3 else 5
// 3:
// t5 = *t0
// t6 = invoke t5.Foo()
// jump 4
// 4:
// return
// 5:
// t7 = *j
// t8 = invoke t7.Foo()
// jump 4
// Flow chain showing that B reaches (*i).foo():
// t3 (B) -> t4 -> t0 -> t5
// Flow chain showing that B reaches (*j).foo():
// t3 (B) -> t4 -> t0 <--> j -> t7
// WANT:
// Local(t0) -> Local(i), Local(j), Local(t5)
// Local(i) -> Local(t0)
// Local(j) -> Local(t0), Local(t7)
// Local(t3) -> Local(t4)
// Local(t4) -> Local(t0)
================================================
FILE: go/callgraph/vta/testdata/src/ranges.go
================================================
// Copyright 2021 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// go:build ignore
package testdata
type I interface {
Foo() string
}
type B struct {
p string
}
func (b B) Foo() string { return b.p }
func Baz(m map[I]*I) {
for i, v := range m {
*v = B{p: i.Foo()}
}
}
// Relevant SSA:
// func Baz(m map[I]*I):
// 0:
// t0 = range m
// jump 1
// 1:
// t1 = next t0
// t2 = extract t1 #0
// if t2 goto 2 else 3
// 2:
// t3 = extract t1 #1
// t4 = extract t1 #2
// t5 = local B (complit)
// t6 = &t5.p [#0]
// t7 = invoke t3.Foo()
// *t6 = t7
// t8 = *t5
// t9 = make I <- B (t8)
// *t4 = t9
// jump 1
// 3:
// return
// WANT:
// MapKey(testdata.I) -> Local(t1[1])
// Local(t1[1]) -> Local(t3)
// MapValue(*testdata.I) -> Local(t1[2])
// Local(t1[2]) -> Local(t4), MapValue(*testdata.I)
// Local(t8) -> Local(t9)
// Local(t9) -> Local(t4)
// Local(t4) -> Local(t1[2])
================================================
FILE: go/callgraph/vta/testdata/src/returns.go
================================================
// Copyright 2021 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// go:build ignore
package testdata
type I interface{}
func Bar(ii I) (I, I) {
return Foo(ii)
}
func Foo(iii I) (I, I) {
return iii, iii
}
func Do(j I) *I {
return &j
}
func Baz(i I) *I {
Bar(i)
return Do(i)
}
// Relevant SSA:
// func Bar(ii I) (I, I):
// t0 = Foo(ii)
// t1 = extract t0 #0
// t2 = extract t0 #1
// return t1, t2
//
// func Foo(iii I) (I, I):
// return iii, iii
//
// func Do(j I) *I:
// t0 = new I (j)
// *t0 = j
// return t0
//
// func Baz(i I):
// t0 = Bar(i)
// t1 = Do(i)
// return t1
// t0 and t1 in the last edge correspond to the nodes
// of Do and Baz. This edge is induced by Do(i).
// WANT:
// Local(i) -> Local(ii), Local(j)
// Local(ii) -> Local(iii)
// Local(iii) -> Return(Foo[0]), Return(Foo[1])
// Local(t1) -> Return(Baz[0])
// Local(t1) -> Return(Bar[0])
// Local(t2) -> Return(Bar[1])
// Local(t0) -> Return(Do[0])
// Return(Do[0]) -> Local(t1)
================================================
FILE: go/callgraph/vta/testdata/src/select.go
================================================
// Copyright 2021 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// go:build ignore
package testdata
type I interface {
Foo() string
}
type J interface {
I
}
type B struct {
p string
}
func (b B) Foo() string { return b.p }
func Baz(b1, b2 B, c1 chan I, c2 chan J) {
for {
select {
case c1 <- b1:
print("b1")
case c2 <- b2:
print("b2")
case <-c1:
print("c1")
case k := <-c2:
print(k.Foo())
return
}
}
}
// Relevant SSA:
// func Baz(b1 B, b2 B, c1 chan I, c2 chan J):
// ...
// t0 = make I <- B (b1)
// t1 = make J <- B (b2)
// t2 = select blocking [c1<-t0, c2<-t1, <-c1, <-c2] (index int, ok bool, I, J)
// t3 = extract t2 #0
// t4 = t73== 0:int
// if t4 goto 2 else 3
// ...
// 8:
// t12 = extract t2 #3
// t13 = invoke t12.Foo()
// t14 = print(t15)
// WANT:
// Local(t0) -> Channel(chan testdata.I)
// Local(t1) -> Channel(chan testdata.J)
// Channel(chan testdata.I) -> Local(t2[2])
// Channel(chan testdata.J) -> Local(t2[3])
================================================
FILE: go/callgraph/vta/testdata/src/simple.go
================================================
// Copyright 2021 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package main
var gl int
type X struct {
a int
b int
}
func main() {
print(gl)
}
func foo() (r int) { return gl }
================================================
FILE: go/callgraph/vta/testdata/src/static_calls.go
================================================
// Copyright 2021 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// go:build ignore
package testdata
type I interface{}
func foo(i I) (I, I) {
return i, i
}
func doWork(ii I) {}
func close(iii I) {}
func Baz(inp I) {
a, b := foo(inp)
defer close(a)
go doWork(b)
}
// Relevant SSA:
// func Baz(inp I):
// t0 = foo(inp)
// t1 = extract t0 #0
// t2 = extract t0 #1
// defer close(t1)
// go doWork(t2)
// rundefers
// ...
// func foo(i I) (I, I):
// return i, i
// WANT:
// Local(inp) -> Local(i)
// Local(t1) -> Local(iii)
// Local(t2) -> Local(ii)
// Local(i) -> Return(foo[0]), Return(foo[1])
// Return(foo[0]) -> Local(t0[0])
// Return(foo[1]) -> Local(t0[1])
================================================
FILE: go/callgraph/vta/testdata/src/store.go
================================================
// Copyright 2021 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// go:build ignore
package testdata
// Tests graph creation for store/load and make instructions.
// Note that ssa package does not have a load instruction per
// se. Yet, one is encoded as a unary instruction with the
// * operator.
type A struct{}
type I interface{ foo() }
func (a A) foo() {}
func main() {
a := A{}
var i I
i = a
ii := &i
(*ii).foo()
}
// Relevant SSA:
// t0 = new I (i)
// t1 = make I <- A (struct{}{}:A) A -> t1
// *t0 = t1 t1 -> t0
// t2 = *t0 t0 -> t2
// t3 = invoke t2.foo()
// return
// WANT:
// Constant(testdata.A) -> Local(t1)
// Local(t1) -> Local(t0)
// Local(t0) -> Local(t2)
================================================
FILE: go/callgraph/vta/testdata/src/store_load_alias.go
================================================
// Copyright 2021 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// go:build ignore
package testdata
type A struct{}
func (a A) foo() {}
type I interface{ foo() }
func Baz(i I) {
j := &i
k := &j
**k = A{}
i.foo()
(**k).foo()
}
// Relevant SSA:
// func Baz(i I):
// t0 = new I (i)
// *t0 = i
// t1 = new *I (j)
// *t1 = t0
// t2 = *t1
// t3 = make I <- A (struct{}{}:A) I
// *t2 = t3
// t4 = *t0
// t5 = invoke t4.foo()
// t6 = *t1
// t7 = *t6
// t8 = invoke t7.foo()
// Flow chain showing that A reaches i.foo():
// Constant(A) -> t3 -> t2 <-> PtrInterface(I) <-> t0 -> t4
// Flow chain showing that A reaches (**k).foo():
// Constant(A) -> t3 -> t2 <-> PtrInterface(I) <-> t6 -> t7
// WANT:
// Local(i) -> Local(t0)
// Local(t0) -> Local(t4), PtrInterface(testdata.I)
// PtrInterface(testdata.I) -> Local(t0), Local(t2), Local(t6)
// Local(t2) -> PtrInterface(testdata.I)
// Constant(testdata.A) -> Local(t3)
// Local(t3) -> Local(t2)
// Local(t6) -> Local(t7), PtrInterface(testdata.I)
================================================
FILE: go/callgraph/vta/testdata/src/stores_arrays.go
================================================
// Copyright 2021 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// go:build ignore
package testdata
type I interface {
Foo()
}
type J interface {
Foo()
Bar()
}
type B struct {
p int
}
func (b B) Foo() {}
func (b B) Bar() {}
func Baz(b *B, S []*I, s []J) {
var x [3]I
x[1] = b
a := &s[2]
(*a).Bar()
print([3]*I{nil, nil, nil}[2])
}
// Relevant SSA:
// func Baz(b *B, S []*I, s []J):
// t0 = local [3]I (x)
// t1 = &t0[1:int]
// ...
// t3 = &s[2:int]
// t4 = *t3
// ...
// t6 = local [3]*I (complit)
// t7 = &t6[0:int]
// ...
// t11 = t10[2:int]
// ...
// WANT:
// Slice([]testdata.I) -> Local(t1)
// Local(t1) -> Slice([]testdata.I)
// Slice([]testdata.J) -> Local(t3)
// Local(t3) -> Local(t4), Slice([]testdata.J)
// Local(t11) -> Slice([]*testdata.I)
// Slice([]*testdata.I) -> Local(t11), PtrInterface(testdata.I)
// Constant(*testdata.I) -> PtrInterface(testdata.I)
================================================
FILE: go/callgraph/vta/testdata/src/t/t.go
================================================
package t
import "d"
func t(i int) int {
data := d.Data{V: i}
return d.D(i) + data.Do()
}
================================================
FILE: go/callgraph/vta/testdata/src/type_assertions.go
================================================
// Copyright 2021 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// go:build ignore
package testdata
// Test program for testing type assertions and extract instructions.
// The latter are tested here too since extract instruction comes
// naturally in type assertions.
type I interface {
Foo()
}
type J interface {
Foo()
Bar()
}
type A struct {
c int
}
func (a A) Foo() {}
func (a A) Bar() {}
func Baz(i I) {
j, ok := i.(J)
if ok {
j.Foo()
}
a := i.(*A)
a.Bar()
}
// Relevant SSA:
// func Baz(i I):
// t0 = typeassert,ok i.(J)
// t1 = extract t0 #0
// t2 = extract t0 #1
// if t2 goto 1 else 2
// 1:
// t3 = invoke t1.Foo()
// jump 2
// 2:
// t4 = typeassert i.(*A) // no flow since t4 is of concrete type
// t5 = *t4
// t6 = (A).Bar(t5)
// return
// WANT:
// Local(i) -> Local(t0[0])
// Local(t0[0]) -> Local(t1)
================================================
FILE: go/callgraph/vta/testdata/src/type_conversions.go
================================================
// Copyright 2021 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// go:build ignore
package testdata
type Y interface {
Foo()
Bar(float64)
}
type Z Y
type W interface {
Y
}
type A struct{}
func (a A) Foo() { print("A:Foo") }
func (a A) Bar(f float64) { print(uint(f)) }
type B struct{}
func (b B) Foo() { print("B:Foo") }
func (b B) Bar(f float64) { print(uint(f) + 1) }
type X interface {
Foo()
}
func Baz(y Y) {
z := Z(y)
z.Foo()
x := X(y)
x.Foo()
y = A{}
var y_p *Y = &y
w_p := (*W)(y_p)
*w_p = B{}
(*y_p).Foo() // prints B:Foo
(*w_p).Foo() // prints B:Foo
}
// Relevant SSA:
// func Baz(y Y):
// t0 = new Y (y)
// *t0 = y
// t1 = *t0
// t2 = changetype Z <- Y (t1)
// t3 = invoke t2.Foo()
//
// t4 = *t0
// t5 = change interface X <- Y (t4)
// t6 = invoke t5.Foo()
//
// t7 = make Y <- A (struct{}{}:A)
// *t0 = t7
// t8 = changetype *W <- *Y (t0)
// t9 = make W <- B (struct{}{}:B)
// *t8 = t9
// t10 = *t0
// t11 = invoke t10.Foo()
// t12 = *t8
// t13 = invoke t12.Foo()
// return
// WANT:
// Local(t1) -> Local(t2)
// Local(t4) -> Local(t5)
// Local(t0) -> Local(t1), Local(t10), Local(t4), Local(t8)
// Local(y) -> Local(t0)
// Constant(testdata.A) -> Local(t7)
// Local(t7) -> Local(t0)
// Local(t9) -> Local(t8)
================================================
FILE: go/callgraph/vta/utils.go
================================================
// Copyright 2021 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package vta
import (
"go/types"
"iter"
"golang.org/x/tools/go/ssa"
"golang.org/x/tools/internal/typeparams"
)
func canAlias(n1, n2 node) bool {
return isReferenceNode(n1) && isReferenceNode(n2)
}
func isReferenceNode(n node) bool {
if _, ok := n.(nestedPtrInterface); ok {
return true
}
if _, ok := n.(nestedPtrFunction); ok {
return true
}
if _, ok := types.Unalias(n.Type()).(*types.Pointer); ok {
return true
}
return false
}
// hasInFlow checks if a concrete type can flow to node `n`.
// Returns yes iff the type of `n` satisfies one the following:
// 1. is an interface
// 2. is a (nested) pointer to interface (needed for, say,
// slice elements of nested pointers to interface type)
// 3. is a function type (needed for higher-order type flow)
// 4. is a (nested) pointer to function (needed for, say,
// slice elements of nested pointers to function type)
// 5. is a global Recover or Panic node
func hasInFlow(n node) bool {
if _, ok := n.(panicArg); ok {
return true
}
if _, ok := n.(recoverReturn); ok {
return true
}
t := n.Type()
if i := interfaceUnderPtr(t); i != nil {
return true
}
if f := functionUnderPtr(t); f != nil {
return true
}
return types.IsInterface(t) || isFunction(t)
}
func isFunction(t types.Type) bool {
_, ok := t.Underlying().(*types.Signature)
return ok
}
// interfaceUnderPtr checks if type `t` is a potentially nested
// pointer to interface and if yes, returns the interface type.
// Otherwise, returns nil.
func interfaceUnderPtr(t types.Type) types.Type {
seen := make(map[types.Type]bool)
var visit func(types.Type) types.Type
visit = func(t types.Type) types.Type {
if seen[t] {
return nil
}
seen[t] = true
p, ok := t.Underlying().(*types.Pointer)
if !ok {
return nil
}
if types.IsInterface(p.Elem()) {
return p.Elem()
}
return visit(p.Elem())
}
return visit(t)
}
// functionUnderPtr checks if type `t` is a potentially nested
// pointer to function type and if yes, returns the function type.
// Otherwise, returns nil.
func functionUnderPtr(t types.Type) types.Type {
seen := make(map[types.Type]bool)
var visit func(types.Type) types.Type
visit = func(t types.Type) types.Type {
if seen[t] {
return nil
}
seen[t] = true
p, ok := t.Underlying().(*types.Pointer)
if !ok {
return nil
}
if isFunction(p.Elem()) {
return p.Elem()
}
return visit(p.Elem())
}
return visit(t)
}
// sliceArrayElem returns the element type of type `t` that is
// expected to be a (pointer to) array, slice or string, consistent with
// the ssa.Index and ssa.IndexAddr instructions. Panics otherwise.
func sliceArrayElem(t types.Type) types.Type {
switch u := t.Underlying().(type) {
case *types.Pointer:
switch e := u.Elem().Underlying().(type) {
case *types.Array:
return e.Elem()
case *types.Interface:
return sliceArrayElem(e) // e is a type param with matching element types.
default:
panic(t)
}
case *types.Array:
return u.Elem()
case *types.Slice:
return u.Elem()
case *types.Basic:
return types.Typ[types.Byte]
case *types.Interface: // type param.
terms, err := typeparams.InterfaceTermSet(u)
if err != nil || len(terms) == 0 {
panic(t)
}
return sliceArrayElem(terms[0].Type()) // Element types must match.
default:
panic(t)
}
}
// siteCallees returns an iterator for the callees for call site `c`.
func siteCallees(c ssa.CallInstruction, callees calleesFunc) iter.Seq[*ssa.Function] {
return func(yield func(*ssa.Function) bool) {
for _, callee := range callees(c) {
if !yield(callee) {
return
}
}
}
}
func canHaveMethods(t types.Type) bool {
t = types.Unalias(t)
if _, ok := t.(*types.Named); ok {
return true
}
u := t.Underlying()
switch u.(type) {
case *types.Interface, *types.Signature, *types.Struct:
return true
default:
return false
}
}
// calls returns the set of call instructions in `f`.
func calls(f *ssa.Function) []ssa.CallInstruction {
var calls []ssa.CallInstruction
for _, bl := range f.Blocks {
for _, instr := range bl.Instrs {
if c, ok := instr.(ssa.CallInstruction); ok {
calls = append(calls, c)
}
}
}
return calls
}
================================================
FILE: go/callgraph/vta/vta.go
================================================
// Copyright 2021 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package vta computes the call graph of a Go program using the Variable
// Type Analysis (VTA) algorithm originally described in "Practical Virtual
// Method Call Resolution for Java," Vijay Sundaresan, Laurie Hendren,
// Chrislain Razafimahefa, Raja Vallée-Rai, Patrick Lam, Etienne Gagnon, and
// Charles Godin.
//
// Note: this package is in experimental phase and its interface is
// subject to change.
// TODO(zpavlinovic): reiterate on documentation.
//
// The VTA algorithm overapproximates the set of types (and function literals)
// a variable can take during runtime by building a global type propagation
// graph and propagating types (and function literals) through the graph.
//
// A type propagation is a directed, labeled graph. A node can represent
// one of the following:
// - A field of a struct type.
// - A local (SSA) variable of a method/function.
// - All pointers to a non-interface type.
// - The return value of a method.
// - All elements in an array.
// - All elements in a slice.
// - All elements in a map.
// - All elements in a channel.
// - A global variable.
//
// In addition, the implementation used in this package introduces
// a few Go specific kinds of nodes:
// - (De)references of nested pointers to interfaces are modeled
// as a unique nestedPtrInterface node in the type propagation graph.
// - Each function literal is represented as a function node whose
// internal value is the (SSA) representation of the function. This
// is done to precisely infer flow of higher-order functions.
//
// Edges in the graph represent flow of types (and function literals) through
// the program. That is, the model 1) typing constraints that are induced by
// assignment statements or function and method calls and 2) higher-order flow
// of functions in the program.
//
// The labeling function maps each node to a set of types and functions that
// can intuitively reach the program construct the node represents. Initially,
// every node is assigned a type corresponding to the program construct it
// represents. Function nodes are also assigned the function they represent.
// The labeling function then propagates types and function through the graph.
//
// The result of VTA is a type propagation graph in which each node is labeled
// with a conservative overapproximation of the set of types (and functions)
// it may have. This information is then used to construct the call graph.
// For each unresolved call site, vta uses the set of types and functions
// reaching the node representing the call site to create a set of callees.
package vta
// TODO(zpavlinovic): update VTA for how it handles generic function bodies and instantiation wrappers.
import (
"go/types"
"golang.org/x/tools/go/callgraph"
"golang.org/x/tools/go/ssa"
)
// CallGraph uses the VTA algorithm to compute call graph for all functions
// f:true in funcs. VTA refines the results of initial call graph and uses it
// to establish interprocedural type flow. If initial is nil, VTA uses a more
// efficient approach to construct a CHA call graph.
//
// The resulting graph does not have a root node.
//
// CallGraph does not make any assumptions on initial types global variables
// and function/method inputs can have. CallGraph is then sound, modulo use of
// reflection and unsafe, if the initial call graph is sound.
func CallGraph(funcs map[*ssa.Function]bool, initial *callgraph.Graph) *callgraph.Graph {
callees := makeCalleesFunc(funcs, initial)
vtaG, canon := typePropGraph(funcs, callees)
types := propagate(vtaG, canon)
c := &constructor{types: types, callees: callees, cache: make(methodCache)}
return c.construct(funcs)
}
// constructor type linearly traverses the input program
// and constructs a callgraph based on the results of the
// VTA type propagation phase.
type constructor struct {
types propTypeMap
cache methodCache
callees calleesFunc
}
func (c *constructor) construct(funcs map[*ssa.Function]bool) *callgraph.Graph {
cg := &callgraph.Graph{Nodes: make(map[*ssa.Function]*callgraph.Node)}
for f, in := range funcs {
if in {
c.constrct(cg, f)
}
}
return cg
}
func (c *constructor) constrct(g *callgraph.Graph, f *ssa.Function) {
caller := g.CreateNode(f)
for _, call := range calls(f) {
for _, c := range c.resolves(call) {
callgraph.AddEdge(caller, call, g.CreateNode(c))
}
}
}
// resolves computes the set of functions to which VTA resolves `c`. The resolved
// functions are intersected with functions to which `c.initial` resolves `c`.
func (c *constructor) resolves(call ssa.CallInstruction) []*ssa.Function {
cc := call.Common()
if cc.StaticCallee() != nil {
return []*ssa.Function{cc.StaticCallee()}
}
// Skip builtins as they are not *ssa.Function.
if _, ok := cc.Value.(*ssa.Builtin); ok {
return nil
}
// Cover the case of dynamic higher-order and interface calls.
var res []*ssa.Function
resolved := resolve(call, c.types, c.cache)
for f := range siteCallees(call, c.callees) {
if _, ok := resolved[f]; ok {
res = append(res, f)
}
}
return res
}
// resolve returns a set of functions `c` resolves to based on the
// type propagation results in `types`.
func resolve(c ssa.CallInstruction, types propTypeMap, cache methodCache) map[*ssa.Function]empty {
fns := make(map[*ssa.Function]empty)
n := local{val: c.Common().Value}
for p := range types.propTypes(n) {
for _, f := range propFunc(p, c, cache) {
fns[f] = empty{}
}
}
return fns
}
// propFunc returns the functions modeled with the propagation type `p`
// assigned to call site `c`. If no such function exists, nil is returned.
func propFunc(p propType, c ssa.CallInstruction, cache methodCache) []*ssa.Function {
if p.f != nil {
return []*ssa.Function{p.f}
}
if c.Common().Method == nil {
return nil
}
return cache.methods(p.typ, c.Common().Method.Name(), c.Parent().Prog)
}
// methodCache serves as a type -> method name -> methods
// cache when computing methods of a type using the
// ssa.Program.MethodSets and ssa.Program.MethodValue
// APIs. The cache is used to speed up querying of
// methods of a type as the mentioned APIs are expensive.
type methodCache map[types.Type]map[string][]*ssa.Function
// methods returns methods of a type `t` named `name`. First consults
// `mc` and otherwise queries `prog` for the method. If no such method
// exists, nil is returned.
func (mc methodCache) methods(t types.Type, name string, prog *ssa.Program) []*ssa.Function {
if ms, ok := mc[t]; ok {
return ms[name]
}
ms := make(map[string][]*ssa.Function)
mset := prog.MethodSets.MethodSet(t)
for i, n := 0, mset.Len(); i < n; i++ {
// f can be nil when t is an interface or some
// other type without any runtime methods.
if f := prog.MethodValue(mset.At(i)); f != nil {
ms[f.Name()] = append(ms[f.Name()], f)
}
}
mc[t] = ms
return ms[name]
}
================================================
FILE: go/callgraph/vta/vta_test.go
================================================
// Copyright 2021 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package vta
import (
"strings"
"testing"
"github.com/google/go-cmp/cmp"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/analysistest"
"golang.org/x/tools/go/analysis/passes/buildssa"
"golang.org/x/tools/go/callgraph/cha"
"golang.org/x/tools/go/ssa"
"golang.org/x/tools/go/ssa/ssautil"
"golang.org/x/tools/internal/testenv"
)
func TestVTACallGraph(t *testing.T) {
errDiff := func(t *testing.T, want, got, missing []string) {
t.Errorf("got:\n%s\n\nwant:\n%s\n\nmissing:\n%s\n\ndiff:\n%s",
strings.Join(got, "\n"),
strings.Join(want, "\n"),
strings.Join(missing, "\n"),
cmp.Diff(got, want)) // to aid debugging
}
files := []string{
"testdata/src/callgraph_static.go",
"testdata/src/callgraph_ho.go",
"testdata/src/callgraph_interfaces.go",
"testdata/src/callgraph_pointers.go",
"testdata/src/callgraph_collections.go",
"testdata/src/callgraph_fields.go",
"testdata/src/callgraph_field_funcs.go",
"testdata/src/callgraph_recursive_types.go",
"testdata/src/callgraph_issue_57756.go",
"testdata/src/callgraph_comma_maps.go",
"testdata/src/callgraph_type_aliases.go", // https://github.com/golang/go/issues/68799
}
if testenv.Go1Point() >= 23 {
files = append(files, "testdata/src/callgraph_range_over_func.go")
}
for _, file := range files {
t.Run(file, func(t *testing.T) {
prog, want, err := testProg(t, file, ssa.BuilderMode(0))
if err != nil {
t.Fatalf("couldn't load test file '%s': %s", file, err)
}
if len(want) == 0 {
t.Fatalf("couldn't find want in `%s`", file)
}
// First test VTA with lazy-CHA initial call graph.
g := CallGraph(ssautil.AllFunctions(prog), nil)
got := callGraphStr(g)
if missing := setdiff(want, got); len(missing) > 0 {
errDiff(t, want, got, missing)
}
// Repeat the test with explicit CHA initial call graph.
g = CallGraph(ssautil.AllFunctions(prog), cha.CallGraph(prog))
got = callGraphStr(g)
if missing := setdiff(want, got); len(missing) > 0 {
errDiff(t, want, got, missing)
}
})
}
}
// TestVTAProgVsFuncSet exemplifies and tests different possibilities
// enabled by having an arbitrary function set as input to CallGraph
// instead of the whole program (i.e., ssautil.AllFunctions(prog)).
func TestVTAProgVsFuncSet(t *testing.T) {
prog, want, err := testProg(t, "testdata/src/callgraph_nested_ptr.go", ssa.BuilderMode(0))
if err != nil {
t.Fatalf("couldn't load test `testdata/src/callgraph_nested_ptr.go`: %s", err)
}
if len(want) == 0 {
t.Fatal("couldn't find want in `testdata/src/callgraph_nested_ptr.go`")
}
allFuncs := ssautil.AllFunctions(prog)
g := CallGraph(allFuncs, cha.CallGraph(prog))
// VTA over the whole program will produce a call graph that
// includes Baz:(**i).Foo -> A.Foo, B.Foo.
got := callGraphStr(g)
if diff := setdiff(want, got); len(diff) > 0 {
t.Errorf("computed callgraph %v should contain %v (diff: %v)", got, want, diff)
}
// Prune the set of program functions to exclude Bar(). This should
// yield a call graph that includes different set of callees for Baz
// Baz:(**i).Foo -> A.Foo
//
// Note that the exclusion of Bar can happen, for instance, if Baz is
// considered an entry point of some data flow analysis and Bar is
// provably (e.g., using CHA forward reachability) unreachable from Baz.
noBarFuncs := make(map[*ssa.Function]bool)
for f, in := range allFuncs {
noBarFuncs[f] = in && (funcName(f) != "Bar")
}
want = []string{"Baz: Do(i) -> Do; invoke t2.Foo() -> A.Foo"}
g = CallGraph(noBarFuncs, cha.CallGraph(prog))
got = callGraphStr(g)
if diff := setdiff(want, got); len(diff) > 0 {
t.Errorf("pruned callgraph %v should contain %v (diff: %v)", got, want, diff)
}
}
// TestVTAPanicMissingDefinitions tests if VTA gracefully handles the case
// where VTA panics when a definition of a function or method is not
// available, which can happen when using analysis package. A successful
// test simply does not panic.
func TestVTAPanicMissingDefinitions(t *testing.T) {
run := func(pass *analysis.Pass) (any, error) {
s := pass.ResultOf[buildssa.Analyzer].(*buildssa.SSA)
CallGraph(ssautil.AllFunctions(s.Pkg.Prog), cha.CallGraph(s.Pkg.Prog))
return nil, nil
}
analyzer := &analysis.Analyzer{
Name: "test",
Doc: "test",
Run: run,
Requires: []*analysis.Analyzer{
buildssa.Analyzer,
},
}
testdata := analysistest.TestData()
res := analysistest.Run(t, testdata, analyzer, "t", "d")
if len(res) != 2 {
t.Errorf("want analysis results for 2 packages; got %v", len(res))
}
for _, r := range res {
if r.Err != nil {
t.Errorf("want no error for package %v; got %v", r.Action.Package.Types.Path(), r.Err)
}
}
}
func TestVTACallGraphGenerics(t *testing.T) {
// TODO(zpavlinovic): add more tests
files := []string{
"testdata/src/arrays_generics.go",
"testdata/src/callgraph_generics.go",
"testdata/src/issue63146.go",
}
for _, file := range files {
t.Run(file, func(t *testing.T) {
prog, want, err := testProg(t, file, ssa.InstantiateGenerics)
if err != nil {
t.Fatalf("couldn't load test file '%s': %s", file, err)
}
if len(want) == 0 {
t.Fatalf("couldn't find want in `%s`", file)
}
g := CallGraph(ssautil.AllFunctions(prog), cha.CallGraph(prog))
got := callGraphStr(g)
if diff := setdiff(want, got); len(diff) != 0 {
t.Errorf("computed callgraph %v should contain %v (diff: %v)", got, want, diff)
logFns(t, prog)
}
})
}
}
func TestVTACallGraphGo117(t *testing.T) {
file := "testdata/src/go117.go"
prog, want, err := testProg(t, file, ssa.BuilderMode(0))
if err != nil {
t.Fatalf("couldn't load test file '%s': %s", file, err)
}
if len(want) == 0 {
t.Fatalf("couldn't find want in `%s`", file)
}
g, _ := typePropGraph(ssautil.AllFunctions(prog), makeCalleesFunc(nil, cha.CallGraph(prog)))
got := vtaGraphStr(g)
if diff := setdiff(want, got); len(diff) != 0 {
t.Errorf("`%s`: want superset of %v;\n got %v", file, want, got)
}
}
================================================
FILE: go/cfg/builder.go
================================================
// Copyright 2016 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package cfg
// This file implements the CFG construction pass.
import (
"fmt"
"go/ast"
"go/token"
)
type builder struct {
blocks []*Block
mayReturn func(*ast.CallExpr) bool
current *Block
lblocks map[string]*lblock // labeled blocks
targets *targets // linked stack of branch targets
}
func (b *builder) stmt(_s ast.Stmt) {
// The label of the current statement. If non-nil, its _goto
// target is always set; its _break and _continue are set only
// within the body of switch/typeswitch/select/for/range.
// It is effectively an additional default-nil parameter of stmt().
var label *lblock
start:
switch s := _s.(type) {
case *ast.BadStmt,
*ast.SendStmt,
*ast.IncDecStmt,
*ast.GoStmt,
*ast.EmptyStmt,
*ast.AssignStmt:
// No effect on control flow.
b.add(s)
case *ast.DeferStmt:
b.add(s)
// Assume conservatively that this behaves like:
// defer func() { recover() }
// so any subsequent panic may act like a return.
b.current.returns = true
case *ast.ExprStmt:
b.add(s)
if call, ok := s.X.(*ast.CallExpr); ok && !b.mayReturn(call) {
// Calls to panic, os.Exit, etc, never return.
b.current = b.newBlock(KindUnreachable, s)
}
case *ast.DeclStmt:
// Treat each var ValueSpec as a separate statement.
d := s.Decl.(*ast.GenDecl)
if d.Tok == token.VAR {
for _, spec := range d.Specs {
if spec, ok := spec.(*ast.ValueSpec); ok {
b.add(spec)
}
}
}
case *ast.LabeledStmt:
label = b.labeledBlock(s.Label, s)
b.jump(label._goto)
b.current = label._goto
_s = s.Stmt
goto start // effectively: tailcall stmt(g, s.Stmt, label)
case *ast.ReturnStmt:
b.current.returns = true
b.add(s)
b.current = b.newBlock(KindUnreachable, s)
case *ast.BranchStmt:
b.branchStmt(s)
case *ast.BlockStmt:
b.stmtList(s.List)
case *ast.IfStmt:
if s.Init != nil {
b.stmt(s.Init)
}
then := b.newBlock(KindIfThen, s)
done := b.newBlock(KindIfDone, s)
_else := done
if s.Else != nil {
_else = b.newBlock(KindIfElse, s)
}
b.add(s.Cond)
b.ifelse(then, _else)
b.current = then
b.stmt(s.Body)
b.jump(done)
if s.Else != nil {
b.current = _else
b.stmt(s.Else)
b.jump(done)
}
b.current = done
case *ast.SwitchStmt:
b.switchStmt(s, label)
case *ast.TypeSwitchStmt:
b.typeSwitchStmt(s, label)
case *ast.SelectStmt:
b.selectStmt(s, label)
case *ast.ForStmt:
b.forStmt(s, label)
case *ast.RangeStmt:
b.rangeStmt(s, label)
default:
panic(fmt.Sprintf("unexpected statement kind: %T", s))
}
}
func (b *builder) stmtList(list []ast.Stmt) {
for _, s := range list {
b.stmt(s)
}
}
func (b *builder) branchStmt(s *ast.BranchStmt) {
var block *Block
switch s.Tok {
case token.BREAK:
if s.Label != nil {
if lb := b.labeledBlock(s.Label, nil); lb != nil {
block = lb._break
}
} else {
for t := b.targets; t != nil && block == nil; t = t.tail {
block = t._break
}
}
case token.CONTINUE:
if s.Label != nil {
if lb := b.labeledBlock(s.Label, nil); lb != nil {
block = lb._continue
}
} else {
for t := b.targets; t != nil && block == nil; t = t.tail {
block = t._continue
}
}
case token.FALLTHROUGH:
for t := b.targets; t != nil && block == nil; t = t.tail {
block = t._fallthrough
}
case token.GOTO:
if s.Label != nil {
block = b.labeledBlock(s.Label, nil)._goto
}
}
if block == nil { // ill-typed (e.g. undefined label)
block = b.newBlock(KindUnreachable, s)
}
b.jump(block)
b.current = b.newBlock(KindUnreachable, s)
}
func (b *builder) switchStmt(s *ast.SwitchStmt, label *lblock) {
if s.Init != nil {
b.stmt(s.Init)
}
if s.Tag != nil {
b.add(s.Tag)
}
done := b.newBlock(KindSwitchDone, s)
if label != nil {
label._break = done
}
// We pull the default case (if present) down to the end.
// But each fallthrough label must point to the next
// body block in source order, so we preallocate a
// body block (fallthru) for the next case.
// Unfortunately this makes for a confusing block order.
var defaultBody *[]ast.Stmt
var defaultFallthrough *Block
var fallthru, defaultBlock *Block
ncases := len(s.Body.List)
for i, clause := range s.Body.List {
body := fallthru
if body == nil {
body = b.newBlock(KindSwitchCaseBody, clause) // first case only
}
// Preallocate body block for the next case.
fallthru = done
if i+1 < ncases {
fallthru = b.newBlock(KindSwitchCaseBody, s.Body.List[i+1])
}
cc := clause.(*ast.CaseClause)
if cc.List == nil {
// Default case.
defaultBody = &cc.Body
defaultFallthrough = fallthru
defaultBlock = body
continue
}
var nextCond *Block
for _, cond := range cc.List {
nextCond = b.newBlock(KindSwitchNextCase, cc)
b.add(cond) // one half of the tag==cond condition
b.ifelse(body, nextCond)
b.current = nextCond
}
b.current = body
b.targets = &targets{
tail: b.targets,
_break: done,
_fallthrough: fallthru,
}
b.stmtList(cc.Body)
b.targets = b.targets.tail
b.jump(done)
b.current = nextCond
}
if defaultBlock != nil {
b.jump(defaultBlock)
b.current = defaultBlock
b.targets = &targets{
tail: b.targets,
_break: done,
_fallthrough: defaultFallthrough,
}
b.stmtList(*defaultBody)
b.targets = b.targets.tail
}
b.jump(done)
b.current = done
}
func (b *builder) typeSwitchStmt(s *ast.TypeSwitchStmt, label *lblock) {
if s.Init != nil {
b.stmt(s.Init)
}
if s.Assign != nil {
b.add(s.Assign)
}
done := b.newBlock(KindSwitchDone, s)
if label != nil {
label._break = done
}
var default_ *ast.CaseClause
for _, clause := range s.Body.List {
cc := clause.(*ast.CaseClause)
if cc.List == nil {
default_ = cc
continue
}
body := b.newBlock(KindSwitchCaseBody, cc)
var next *Block
for _, casetype := range cc.List {
next = b.newBlock(KindSwitchNextCase, cc)
// casetype is a type, so don't call b.add(casetype).
// This block logically contains a type assertion,
// x.(casetype), but it's unclear how to represent x.
_ = casetype
b.ifelse(body, next)
b.current = next
}
b.current = body
b.typeCaseBody(cc, done)
b.current = next
}
if default_ != nil {
b.typeCaseBody(default_, done)
} else {
b.jump(done)
}
b.current = done
}
func (b *builder) typeCaseBody(cc *ast.CaseClause, done *Block) {
b.targets = &targets{
tail: b.targets,
_break: done,
}
b.stmtList(cc.Body)
b.targets = b.targets.tail
b.jump(done)
}
func (b *builder) selectStmt(s *ast.SelectStmt, label *lblock) {
// First evaluate channel expressions.
// TODO(adonovan): fix: evaluate only channel exprs here.
for _, clause := range s.Body.List {
if comm := clause.(*ast.CommClause).Comm; comm != nil {
b.stmt(comm)
}
}
done := b.newBlock(KindSelectDone, s)
if label != nil {
label._break = done
}
var defaultBody *[]ast.Stmt
for _, cc := range s.Body.List {
clause := cc.(*ast.CommClause)
if clause.Comm == nil {
defaultBody = &clause.Body
continue
}
body := b.newBlock(KindSelectCaseBody, clause)
next := b.newBlock(KindSelectAfterCase, clause)
b.ifelse(body, next)
b.current = body
b.targets = &targets{
tail: b.targets,
_break: done,
}
switch comm := clause.Comm.(type) {
case *ast.ExprStmt: // <-ch
// nop
case *ast.AssignStmt: // x := <-states[state].Chan
b.add(comm.Lhs[0])
}
b.stmtList(clause.Body)
b.targets = b.targets.tail
b.jump(done)
b.current = next
}
if defaultBody != nil {
b.targets = &targets{
tail: b.targets,
_break: done,
}
b.stmtList(*defaultBody)
b.targets = b.targets.tail
b.jump(done)
}
b.current = done
}
func (b *builder) forStmt(s *ast.ForStmt, label *lblock) {
// ...init...
// jump loop
// loop:
// if cond goto body else done
// body:
// ...body...
// jump post
// post: (target of continue)
// ...post...
// jump loop
// done: (target of break)
if s.Init != nil {
b.stmt(s.Init)
}
body := b.newBlock(KindForBody, s)
done := b.newBlock(KindForDone, s) // target of 'break'
loop := body // target of back-edge
if s.Cond != nil {
loop = b.newBlock(KindForLoop, s)
}
cont := loop // target of 'continue'
if s.Post != nil {
cont = b.newBlock(KindForPost, s)
}
if label != nil {
label._break = done
label._continue = cont
}
b.jump(loop)
b.current = loop
if loop != body {
b.add(s.Cond)
b.ifelse(body, done)
b.current = body
}
b.targets = &targets{
tail: b.targets,
_break: done,
_continue: cont,
}
b.stmt(s.Body)
b.targets = b.targets.tail
b.jump(cont)
if s.Post != nil {
b.current = cont
b.stmt(s.Post)
b.jump(loop) // back-edge
}
b.current = done
}
func (b *builder) rangeStmt(s *ast.RangeStmt, label *lblock) {
b.add(s.X)
if s.Key != nil {
b.add(s.Key)
}
if s.Value != nil {
b.add(s.Value)
}
// ...
// loop: (target of continue)
// if ... goto body else done
// body:
// ...
// jump loop
// done: (target of break)
loop := b.newBlock(KindRangeLoop, s)
b.jump(loop)
b.current = loop
body := b.newBlock(KindRangeBody, s)
done := b.newBlock(KindRangeDone, s)
b.ifelse(body, done)
b.current = body
if label != nil {
label._break = done
label._continue = loop
}
b.targets = &targets{
tail: b.targets,
_break: done,
_continue: loop,
}
b.stmt(s.Body)
b.targets = b.targets.tail
b.jump(loop) // back-edge
b.current = done
}
// -------- helpers --------
// Destinations associated with unlabeled for/switch/select stmts.
// We push/pop one of these as we enter/leave each construct and for
// each BranchStmt we scan for the innermost target of the right type.
type targets struct {
tail *targets // rest of stack
_break *Block
_continue *Block
_fallthrough *Block
}
// Destinations associated with a labeled block.
// We populate these as labels are encountered in forward gotos or
// labeled statements.
type lblock struct {
_goto *Block
_break *Block
_continue *Block
}
// labeledBlock returns the branch target associated with the
// specified label, creating it if needed.
func (b *builder) labeledBlock(label *ast.Ident, stmt *ast.LabeledStmt) *lblock {
lb := b.lblocks[label.Name]
if lb == nil {
lb = &lblock{_goto: b.newBlock(KindLabel, nil)}
if b.lblocks == nil {
b.lblocks = make(map[string]*lblock)
}
b.lblocks[label.Name] = lb
}
// Fill in the label later (in case of forward goto).
// Stmt may be set already if labels are duplicated (ill-typed).
if stmt != nil && lb._goto.Stmt == nil {
lb._goto.Stmt = stmt
}
return lb
}
// newBlock appends a new unconnected basic block to b.cfg's block
// slice and returns it.
// It does not automatically become the current block.
// comment is an optional string for more readable debugging output.
func (b *builder) newBlock(kind BlockKind, stmt ast.Stmt) *Block {
block := &Block{
Index: int32(len(b.blocks)),
Kind: kind,
Stmt: stmt,
}
block.Succs = block.succs2[:0]
b.blocks = append(b.blocks, block)
return block
}
func (b *builder) add(n ast.Node) {
b.current.Nodes = append(b.current.Nodes, n)
}
// jump adds an edge from the current block to the target block,
// and sets b.current to nil.
func (b *builder) jump(target *Block) {
b.current.Succs = append(b.current.Succs, target)
b.current = nil
}
// ifelse emits edges from the current block to the t and f blocks,
// and sets b.current to nil.
func (b *builder) ifelse(t, f *Block) {
b.current.Succs = append(b.current.Succs, t, f)
b.current = nil
}
================================================
FILE: go/cfg/cfg.go
================================================
// Copyright 2016 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package cfg constructs a simple control-flow graph (CFG) of the
// statements and expressions within a single function.
//
// Use cfg.New to construct the CFG for a function body.
//
// The blocks of the CFG contain all the function's non-control
// statements. The CFG does not contain control statements such as If,
// Switch, Select, and Branch, but does contain their subexpressions;
// also, each block records the control statement (Block.Stmt) that
// gave rise to it and its relationship (Block.Kind) to that statement.
//
// For example, this source code:
//
// if x := f(); x != nil {
// T()
// } else {
// F()
// }
//
// produces this CFG:
//
// 1: x := f() Body
// x != nil
// succs: 2, 3
// 2: T() IfThen
// succs: 4
// 3: F() IfElse
// succs: 4
// 4: IfDone
//
// The CFG does contain Return statements; even implicit returns are
// materialized (at the position of the function's closing brace).
//
// The CFG does not record conditions associated with conditional branch
// edges, nor the short-circuit semantics of the && and || operators,
// nor abnormal control flow caused by panic. If you need this
// information, use golang.org/x/tools/go/ssa instead.
package cfg
import (
"bytes"
"fmt"
"go/ast"
"go/format"
"go/token"
)
// A CFG represents the control-flow graph of a single function.
//
// The entry point is Blocks[0]; there may be multiple return blocks.
type CFG struct {
Blocks []*Block // block[0] is entry; order otherwise undefined
noreturn bool // function body lacks a reachable return statement
}
// NoReturn reports whether the function has no reachable return.
func (cfg *CFG) NoReturn() bool { return cfg.noreturn }
// A Block represents a basic block: a list of statements and
// expressions that are always evaluated sequentially.
//
// A block may have 0-2 successors: zero for a return block or a block
// that calls a function such as panic that never returns; one for a
// normal (jump) block; and two for a conditional (if) block.
//
// In a conditional block, the last entry in Nodes is the condition and always
// an [ast.Expr], Succs[0] is the successor if the condition is true, and
// Succs[1] is the successor if the condition is false.
type Block struct {
Nodes []ast.Node // statements, expressions, and ValueSpecs
Succs []*Block // successor nodes in the graph
Index int32 // index within CFG.Blocks
Live bool // block is reachable from entry
returns bool // block contains return or defer (which may recover and return)
Kind BlockKind // block kind
Stmt ast.Stmt // statement that gave rise to this block (see BlockKind for details)
succs2 [2]*Block // underlying array for Succs
}
// A BlockKind identifies the purpose of a block.
// It also determines the possible types of its Stmt field.
type BlockKind uint8
const (
KindInvalid BlockKind = iota // Stmt=nil
KindUnreachable // unreachable block after {Branch,Return}Stmt / no-return call ExprStmt
KindBody // function body BlockStmt
KindForBody // body of ForStmt
KindForDone // block after ForStmt
KindForLoop // head of ForStmt
KindForPost // post condition of ForStmt
KindIfDone // block after IfStmt
KindIfElse // else block of IfStmt
KindIfThen // then block of IfStmt
KindLabel // labeled block of BranchStmt (Stmt may be nil for dangling label)
KindRangeBody // body of RangeStmt
KindRangeDone // block after RangeStmt
KindRangeLoop // head of RangeStmt
KindSelectCaseBody // body of SelectStmt
KindSelectDone // block after SelectStmt
KindSelectAfterCase // block after a CommClause
KindSwitchCaseBody // body of CaseClause
KindSwitchDone // block after {Type.}SwitchStmt
KindSwitchNextCase // secondary expression of a multi-expression CaseClause
)
func (kind BlockKind) String() string {
return [...]string{
KindInvalid: "Invalid",
KindUnreachable: "Unreachable",
KindBody: "Body",
KindForBody: "ForBody",
KindForDone: "ForDone",
KindForLoop: "ForLoop",
KindForPost: "ForPost",
KindIfDone: "IfDone",
KindIfElse: "IfElse",
KindIfThen: "IfThen",
KindLabel: "Label",
KindRangeBody: "RangeBody",
KindRangeDone: "RangeDone",
KindRangeLoop: "RangeLoop",
KindSelectCaseBody: "SelectCaseBody",
KindSelectDone: "SelectDone",
KindSelectAfterCase: "SelectAfterCase",
KindSwitchCaseBody: "SwitchCaseBody",
KindSwitchDone: "SwitchDone",
KindSwitchNextCase: "SwitchNextCase",
}[kind]
}
// New returns a new control-flow graph for the specified function body,
// which must be non-nil.
//
// The CFG builder calls mayReturn to determine whether a given function
// call may return. For example, calls to panic, os.Exit, and log.Fatal
// do not return, so the builder can remove infeasible graph edges
// following such calls. The builder calls mayReturn only for a
// CallExpr beneath an ExprStmt.
func New(body *ast.BlockStmt, mayReturn func(*ast.CallExpr) bool) *CFG {
b := builder{
mayReturn: mayReturn,
}
b.current = b.newBlock(KindBody, body)
b.stmt(body)
// Compute liveness (reachability from entry point),
// breadth-first, marking Block.Live flags.
q := make([]*Block, 0, len(b.blocks))
q = append(q, b.blocks[0]) // entry point
for len(q) > 0 {
b := q[len(q)-1]
q = q[:len(q)-1]
if !b.Live {
b.Live = true
q = append(q, b.Succs...)
}
}
// Does control fall off the end of the function's body?
// Make implicit return explicit.
if b.current != nil && b.current.Live {
b.current.returns = true
b.add(&ast.ReturnStmt{
Return: body.End() - 1,
})
}
// Is any return (or defer+recover) block reachable?
noreturn := true
for _, bl := range b.blocks {
if bl.Live && bl.returns {
noreturn = false
break
}
}
return &CFG{Blocks: b.blocks, noreturn: noreturn}
}
func (b *Block) String() string {
return fmt.Sprintf("block %d (%s)", b.Index, b.comment(nil))
}
func (b *Block) comment(fset *token.FileSet) string {
s := b.Kind.String()
if fset != nil && b.Stmt != nil {
s = fmt.Sprintf("%s@L%d", s, fset.Position(b.Stmt.Pos()).Line)
}
return s
}
// Return returns the return statement at the end of this block if present, nil
// otherwise.
//
// When control falls off the end of the function, the ReturnStmt is synthetic
// and its [ast.Node.End] position may be beyond the end of the file.
//
// A function that contains no return statement (explicit or implied)
// may yet return normally, and may even return a nonzero value. For example:
//
// func() (res any) {
// defer func() { res = recover() }()
// panic(123)
// }
func (b *Block) Return() (ret *ast.ReturnStmt) {
if len(b.Nodes) > 0 {
ret, _ = b.Nodes[len(b.Nodes)-1].(*ast.ReturnStmt)
}
return
}
// Format formats the control-flow graph for ease of debugging.
func (g *CFG) Format(fset *token.FileSet) string {
var buf bytes.Buffer
for _, b := range g.Blocks {
fmt.Fprintf(&buf, ".%d: # %s\n", b.Index, b.comment(fset))
for _, n := range b.Nodes {
fmt.Fprintf(&buf, "\t%s\n", formatNode(fset, n))
}
if len(b.Succs) > 0 {
fmt.Fprintf(&buf, "\tsuccs:")
for _, succ := range b.Succs {
fmt.Fprintf(&buf, " %d", succ.Index)
}
buf.WriteByte('\n')
}
buf.WriteByte('\n')
}
return buf.String()
}
// Dot returns the control-flow graph in the [Dot graph description language].
// Use a command such as 'dot -Tsvg' to render it in a form viewable in a browser.
// This method is provided as a debugging aid; the details of the
// output are unspecified and may change.
//
// [Dot graph description language]: https://en.wikipedia.org/wiki/DOT_(graph_description_language)
func (g *CFG) Dot(fset *token.FileSet) string {
var buf bytes.Buffer
buf.WriteString("digraph CFG {\n")
buf.WriteString(" node [shape=box];\n")
for _, b := range g.Blocks {
// node label
var text bytes.Buffer
text.WriteString(b.comment(fset))
for _, n := range b.Nodes {
fmt.Fprintf(&text, "\n%s", formatNode(fset, n))
}
// node and edges
fmt.Fprintf(&buf, " n%d [label=%q];\n", b.Index, &text)
for _, succ := range b.Succs {
fmt.Fprintf(&buf, " n%d -> n%d;\n", b.Index, succ.Index)
}
}
buf.WriteString("}\n")
return buf.String()
}
func formatNode(fset *token.FileSet, n ast.Node) string {
var buf bytes.Buffer
format.Node(&buf, fset, n)
// Indent secondary lines by a tab.
return string(bytes.Replace(buf.Bytes(), []byte("\n"), []byte("\n\t"), -1))
}
================================================
FILE: go/cfg/cfg_test.go
================================================
// Copyright 2016 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package cfg_test
import (
"bytes"
"fmt"
"go/ast"
"go/format"
"go/parser"
"go/token"
"testing"
"golang.org/x/tools/go/cfg"
"golang.org/x/tools/go/packages"
"golang.org/x/tools/internal/testenv"
)
const src = `package main
import "log"
func f1() {
live()
return
dead()
}
func f2() {
for {
live()
}
dead()
}
func f3() {
if true { // even known values are ignored
return
}
for true { // even known values are ignored
live()
}
for {
live()
}
dead()
}
func f4(x int) {
switch x {
case 1:
live()
fallthrough
case 2:
live()
log.Fatal()
default:
panic("oops")
}
dead()
}
func f4(ch chan int) {
select {
case <-ch:
live()
return
default:
live()
panic("oops")
}
dead()
}
func f5(unknown bool) {
for {
if unknown {
break
}
continue
dead()
}
live()
}
func f6(unknown bool) {
outer:
for {
for {
break outer
dead()
}
dead()
}
live()
}
func f7() {
for {
break nosuchlabel
dead()
}
dead()
}
func f8() {
select{}
dead()
}
func f9(ch chan int) {
select {
case <-ch:
return
}
dead()
}
func f10(ch chan int) {
select {
case <-ch:
return
dead()
default:
}
live()
}
`
func TestDeadCode(t *testing.T) {
// We'll use dead code detection to verify the CFG.
fset := token.NewFileSet()
f, err := parser.ParseFile(fset, "dummy.go", src, parser.Mode(0))
if err != nil {
t.Fatal(err)
}
for _, decl := range f.Decls {
if decl, ok := decl.(*ast.FuncDecl); ok {
g := cfg.New(decl.Body, mayReturn)
// Print statements in unreachable blocks
// (in order determined by builder).
var buf bytes.Buffer
for _, b := range g.Blocks {
if !b.Live {
for _, n := range b.Nodes {
fmt.Fprintf(&buf, "\t%s\n", formatNode(fset, n))
}
}
}
// Check that the result contains "dead" at least once but not "live".
if !bytes.Contains(buf.Bytes(), []byte("dead")) ||
bytes.Contains(buf.Bytes(), []byte("live")) {
t.Errorf("unexpected dead statements in function %s:\n%s",
decl.Name.Name,
&buf)
t.Logf("control flow graph:\n%s", g.Format(fset))
}
}
}
}
// TestSmoke runs the CFG builder on every FuncDecl in the standard
// library and x/tools. (This is all well-typed code, but it gives
// some coverage.)
func TestSmoke(t *testing.T) {
if testing.Short() {
t.Skip("skipping in short mode")
}
testenv.NeedsTool(t, "go")
// The Mode API is just hateful.
// https://github.com/golang/go/issues/48226#issuecomment-1948792315
mode := packages.NeedDeps | packages.NeedImports | packages.NeedSyntax | packages.NeedTypes
pkgs, err := packages.Load(&packages.Config{Mode: mode}, "std", "golang.org/x/tools/...")
if err != nil {
t.Fatal(err)
}
for _, pkg := range pkgs {
for _, file := range pkg.Syntax {
for _, decl := range file.Decls {
if decl, ok := decl.(*ast.FuncDecl); ok && decl.Body != nil {
g := cfg.New(decl.Body, mayReturn)
// Run a few quick sanity checks.
failed := false
for i, b := range g.Blocks {
errorf := func(format string, args ...any) {
if !failed {
t.Errorf("%s\n%s", pkg.Fset.Position(decl.Pos()), g.Format(pkg.Fset))
failed = true
}
msg := fmt.Sprintf(format, args...)
t.Errorf("block %d: %s", i, msg)
}
if b.Kind == cfg.KindInvalid {
errorf("invalid Block.Kind %v", b.Kind)
}
if b.Stmt == nil && b.Kind != cfg.KindLabel {
errorf("nil Block.Stmt (Kind=%v)", b.Kind)
}
if i != int(b.Index) {
errorf("invalid Block.Index")
}
}
}
}
}
}
}
// A trivial mayReturn predicate that looks only at syntax, not types.
func mayReturn(call *ast.CallExpr) bool {
switch fun := call.Fun.(type) {
case *ast.Ident:
return fun.Name != "panic"
case *ast.SelectorExpr:
return fun.Sel.Name != "Fatal"
}
return true
}
func formatNode(fset *token.FileSet, n ast.Node) string {
var buf bytes.Buffer
format.Node(&buf, fset, n)
// Indent secondary lines by a tab.
return string(bytes.Replace(buf.Bytes(), []byte("\n"), []byte("\n\t"), -1))
}
================================================
FILE: go/cfg/main.go
================================================
//go:build ignore
// Copyright 2024 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// The cfg command prints the control-flow graph of the first function
// or method whose name matches 'funcname' in the specified package.
//
// Usage: cfg package funcname
//
// Example:
//
// $ go build -o cfg ./go/cfg/main.go
// $ cfg ./go/cfg stmt | dot -Tsvg > cfg.svg && open cfg.svg
package main
import (
"flag"
"fmt"
"go/ast"
"log"
"os"
"golang.org/x/tools/go/cfg"
"golang.org/x/tools/go/packages"
)
func main() {
flag.Parse()
if len(flag.Args()) != 2 {
log.Fatal("Usage: package funcname")
}
pattern, funcname := flag.Args()[0], flag.Args()[1]
pkgs, err := packages.Load(&packages.Config{Mode: packages.LoadSyntax}, pattern)
if err != nil {
log.Fatal(err)
}
if packages.PrintErrors(pkgs) > 0 {
os.Exit(1)
}
for _, pkg := range pkgs {
for _, f := range pkg.Syntax {
for _, decl := range f.Decls {
if decl, ok := decl.(*ast.FuncDecl); ok {
if decl.Name.Name == funcname {
g := cfg.New(decl.Body, mayReturn)
fmt.Println(g.Dot(pkg.Fset))
os.Exit(0)
}
}
}
}
}
log.Fatalf("no function %q found in %s", funcname, pattern)
}
// A trivial mayReturn predicate that looks only at syntax, not types.
func mayReturn(call *ast.CallExpr) bool {
switch fun := call.Fun.(type) {
case *ast.Ident:
return fun.Name != "panic"
case *ast.SelectorExpr:
return fun.Sel.Name != "Fatal"
}
return true
}
================================================
FILE: go/gccgoexportdata/gccgoexportdata.go
================================================
// Copyright 2016 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package gccgoexportdata provides functions for reading export data
// files containing type information produced by the gccgo compiler.
//
// This package is a stop-gap until such time as gccgo uses the same
// export data format as gc; see Go issue 17573. Once that occurs, this
// package will be deprecated and eventually deleted.
package gccgoexportdata
// TODO(adonovan): add Find, Write, Importer to the API,
// for symmetry with gcexportdata.
import (
"bytes"
"debug/elf"
"fmt"
"go/token"
"go/types"
"io"
"strconv"
"strings"
"golang.org/x/tools/go/internal/gccgoimporter"
)
// CompilerInfo executes the specified gccgo compiler and returns
// information about it: its version (e.g. "4.8.0"), its target triple
// (e.g. "x86_64-unknown-linux-gnu"), and the list of directories it
// searches to find standard packages. The given arguments are passed
// directly to calls to the specified gccgo compiler.
func CompilerInfo(gccgo string, args ...string) (version, triple string, dirs []string, err error) {
var inst gccgoimporter.GccgoInstallation
err = inst.InitFromDriver(gccgo, args...)
if err == nil {
version = inst.GccVersion
triple = inst.TargetTriple
dirs = inst.SearchPaths()
}
return
}
// NewReader returns a reader for the export data section of an object
// (.o) or archive (.a) file read from r.
func NewReader(r io.Reader) (io.Reader, error) {
data, err := io.ReadAll(r)
if err != nil {
return nil, err
}
// If the file is an archive, extract the first section.
const archiveMagic = "!\n"
if bytes.HasPrefix(data, []byte(archiveMagic)) {
section, err := firstSection(data[len(archiveMagic):])
if err != nil {
return nil, err
}
data = section
}
// Data contains an ELF file with a .go_export section.
// ELF magic number is "\x7fELF".
ef, err := elf.NewFile(bytes.NewReader(data))
if err != nil {
return nil, err
}
sec := ef.Section(".go_export")
if sec == nil {
return nil, fmt.Errorf("no .go_export section")
}
return sec.Open(), nil
}
// firstSection returns the contents of the first regular file in an ELF
// archive (http://www.sco.com/developers/devspecs/gabi41.pdf, §7.2).
func firstSection(a []byte) ([]byte, error) {
for len(a) >= 60 {
var hdr []byte
hdr, a = a[:60], a[60:]
name := strings.TrimSpace(string(hdr[:16]))
sizeStr := string(hdr[48:58])
size, err := strconv.Atoi(strings.TrimSpace(sizeStr))
if err != nil {
return nil, fmt.Errorf("invalid size: %q", sizeStr)
}
if len(a) < size {
return nil, fmt.Errorf("invalid section size: %d", size)
}
// The payload is padded to an even number of bytes.
var payload []byte
payload, a = a[:size], a[size+size&1:]
// Skip special files:
// "/" archive symbol table
// "/SYM64/" archive symbol table on e.g. s390x
// "//" archive string table (if any filename is >15 bytes)
if name == "/" || name == "/SYM64/" || name == "//" {
continue
}
return payload, nil
}
return nil, fmt.Errorf("archive has no regular sections")
}
// Read reads export data from in, decodes it, and returns type
// information for the package.
// The package name is specified by path.
//
// The FileSet parameter is currently unused but exists for symmetry
// with gcexportdata.
//
// Read may inspect and add to the imports map to ensure that references
// within the export data to other packages are consistent. The caller
// must ensure that imports[path] does not exist, or exists but is
// incomplete (see types.Package.Complete), and Read inserts the
// resulting package into this map entry.
//
// On return, the state of the reader is undefined.
func Read(in io.Reader, _ *token.FileSet, imports map[string]*types.Package, path string) (*types.Package, error) {
return gccgoimporter.Parse(in, imports, path)
}
================================================
FILE: go/gccgoexportdata/gccgoexportdata_test.go
================================================
// Copyright 2016 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package gccgoexportdata_test
import (
"go/types"
"os"
"testing"
"golang.org/x/tools/go/gccgoexportdata"
)
// Test ensures this package can read gccgo export data from the
// .go_export from a standalone ELF file or such a file in an archive
// library.
//
// The testdata/{short,long}.a ELF archive files were produced by:
//
// $ echo 'package foo; func F()' > foo.go
// $ gccgo -c -fgo-pkgpath blah foo.go
// $ objcopy -j .go_export foo.o foo.gox
// $ ar q short.a foo.gox
// $ objcopy -j .go_export foo.o name-longer-than-16-bytes.gox
// $ ar q long.a name-longer-than-16-bytes.gox
//
// The file long.a contains an archive string table.
//
// The errors.gox file (an ELF object file) comes from the toolchain's
// standard library.
func Test(t *testing.T) {
for _, test := range []struct {
filename, path, member, wantType string
}{
{"testdata/errors.gox", "errors", "New", "func(text string) error"},
{"testdata/short.a", "short", "F", "func()"},
{"testdata/long.a", "long", "F", "func()"},
} {
t.Logf("filename = %s", test.filename)
f, err := os.Open(test.filename)
if err != nil {
t.Error(err)
continue
}
defer f.Close()
r, err := gccgoexportdata.NewReader(f)
if err != nil {
t.Error(err)
continue
}
imports := make(map[string]*types.Package)
pkg, err := gccgoexportdata.Read(r, nil, imports, test.path)
if err != nil {
t.Error(err)
continue
}
// Check type of designated package member.
obj := pkg.Scope().Lookup(test.member)
if obj == nil {
t.Errorf("%s.%s not found", test.path, test.member)
continue
}
if obj.Type().String() != test.wantType {
t.Errorf("%s.%s.Type = %s, want %s",
test.path, test.member, obj.Type(), test.wantType)
}
}
}
================================================
FILE: go/gcexportdata/example_test.go
================================================
// Copyright 2016 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build go1.7 && gc && !android && !ios && (unix || aix || darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris || plan9 || windows)
package gcexportdata_test
import (
"fmt"
"go/ast"
"go/parser"
"go/token"
"go/types"
"log"
"os"
"path/filepath"
"slices"
"strings"
"golang.org/x/tools/go/gcexportdata"
)
// ExampleRead uses gcexportdata.Read to load type information for the
// "fmt" package from the fmt.a file produced by the gc compiler.
func ExampleRead() {
// Find the export data file.
filename, path := gcexportdata.Find("fmt", "")
if filename == "" {
log.Fatalf("can't find export data for fmt")
}
fmt.Printf("Package path: %s\n", path)
// Open and read the file.
f, err := os.Open(filename)
if err != nil {
log.Fatal(err)
}
defer f.Close()
r, err := gcexportdata.NewReader(f)
if err != nil {
log.Fatalf("reading export data %s: %v", filename, err)
}
// Decode the export data.
fset := token.NewFileSet()
imports := make(map[string]*types.Package)
pkg, err := gcexportdata.Read(r, fset, imports, path)
if err != nil {
log.Fatal(err)
}
// We can see all the names in Names.
members := pkg.Scope().Names()
foundPrintln := slices.Contains(members, "Println")
fmt.Print("Package members: ")
if foundPrintln {
fmt.Println("Println found")
} else {
fmt.Println("Println not found")
}
// We can also look up a name directly using Lookup.
println := pkg.Scope().Lookup("Println")
// go 1.18+ uses the 'any' alias
typ := strings.ReplaceAll(println.Type().String(), "interface{}", "any")
fmt.Printf("Println type: %s\n", typ)
posn := fset.Position(println.Pos())
// make example deterministic
posn.Line = 123
fmt.Printf("Println location: %s\n", slashify(posn))
// Output:
//
// Package path: fmt
// Package members: Println found
// Println type: func(a ...any) (n int, err error)
// Println location: $GOROOT/src/fmt/print.go:123:1
}
// ExampleNewImporter demonstrates usage of NewImporter to provide type
// information for dependencies when type-checking Go source code.
func ExampleNewImporter() {
const src = `package myrpc
// choosing a package that doesn't change across releases
import "net/rpc"
const serverError rpc.ServerError = ""
`
fset := token.NewFileSet()
f, err := parser.ParseFile(fset, "myrpc.go", src, 0)
if err != nil {
log.Fatal(err)
}
packages := make(map[string]*types.Package)
imp := gcexportdata.NewImporter(fset, packages)
conf := types.Config{Importer: imp}
pkg, err := conf.Check("myrpc", fset, []*ast.File{f}, nil)
if err != nil {
log.Fatal(err)
}
// object from imported package
pi := packages["net/rpc"].Scope().Lookup("ServerError")
fmt.Printf("type %s.%s %s // %s\n",
pi.Pkg().Path(),
pi.Name(),
pi.Type().Underlying(),
slashify(fset.Position(pi.Pos())),
)
// object in source package
twopi := pkg.Scope().Lookup("serverError")
fmt.Printf("const %s %s = %s // %s\n",
twopi.Name(),
twopi.Type(),
twopi.(*types.Const).Val(),
slashify(fset.Position(twopi.Pos())),
)
// Output:
//
// type net/rpc.ServerError string // $GOROOT/src/net/rpc/client.go:20:1
// const serverError net/rpc.ServerError = "" // myrpc.go:6:7
}
func slashify(posn token.Position) token.Position {
posn.Filename = filepath.ToSlash(posn.Filename) // for MS Windows portability
return posn
}
================================================
FILE: go/gcexportdata/gcexportdata.go
================================================
// Copyright 2016 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package gcexportdata provides functions for reading and writing
// export data, which is a serialized description of the API of a Go
// package including the names, kinds, types, and locations of all
// exported declarations.
//
// The standard Go compiler (cmd/compile) writes an export data file
// for each package it compiles, which it later reads when compiling
// packages that import the earlier one. The compiler must thus
// contain logic to both write and read export data.
// (See the "Export" section in the cmd/compile/README file.)
//
// The [Read] function in this package can read files produced by the
// compiler, producing [go/types] data structures. As a matter of
// policy, Read supports export data files produced by only the last
// two Go releases plus tip; see https://go.dev/issue/68898. The
// export data files produced by the compiler contain additional
// details related to generics, inlining, and other optimizations that
// cannot be decoded by the [Read] function.
//
// In files written by the compiler, the export data is not at the
// start of the file. Before calling Read, use [NewReader] to locate
// the desired portion of the file.
//
// The [Write] function in this package encodes the exported API of a
// Go package ([types.Package]) as a file. Such files can be later
// decoded by Read, but cannot be consumed by the compiler.
//
// # Future changes
//
// Although Read supports the formats written by both Write and the
// compiler, the two are quite different, and there is an open
// proposal (https://go.dev/issue/69491) to separate these APIs.
//
// Under that proposal, this package would ultimately provide only the
// Read operation for compiler export data, which must be defined in
// this module (golang.org/x/tools), not in the standard library, to
// avoid version skew for developer tools that need to read compiler
// export data both before and after a Go release, such as from Go
// 1.23 to Go 1.24. Because this package lives in the tools module,
// clients can update their version of the module some time before the
// Go 1.24 release and rebuild and redeploy their tools, which will
// then be able to consume both Go 1.23 and Go 1.24 export data files,
// so they will work before and after the Go update. (See discussion
// at https://go.dev/issue/15651.)
//
// The operations to import and export [go/types] data structures
// would be defined in the go/types package as Import and Export.
// [Write] would (eventually) delegate to Export,
// and [Read], when it detects a file produced by Export,
// would delegate to Import.
//
// # Deprecations
//
// The [NewImporter] and [Find] functions are deprecated and should
// not be used in new code. The [WriteBundle] and [ReadBundle]
// functions are experimental, and there is an open proposal to
// deprecate them (https://go.dev/issue/69573).
package gcexportdata
import (
"bufio"
"bytes"
"encoding/json"
"fmt"
"go/token"
"go/types"
"io"
"os/exec"
"golang.org/x/tools/internal/gcimporter"
)
// Find returns the name of an object (.o) or archive (.a) file
// containing type information for the specified import path,
// using the go command.
// If no file was found, an empty filename is returned.
//
// A relative srcDir is interpreted relative to the current working directory.
//
// Find also returns the package's resolved (canonical) import path,
// reflecting the effects of srcDir and vendoring on importPath.
//
// Deprecated: Use the higher-level API in golang.org/x/tools/go/packages,
// which is more efficient.
func Find(importPath, srcDir string) (filename, path string) {
cmd := exec.Command("go", "list", "-json", "-export", "--", importPath)
cmd.Dir = srcDir
out, err := cmd.Output()
if err != nil {
return "", ""
}
var data struct {
ImportPath string
Export string
}
json.Unmarshal(out, &data)
return data.Export, data.ImportPath
}
// NewReader returns a reader for the export data section of an object
// (.o) or archive (.a) file read from r. The new reader may provide
// additional trailing data beyond the end of the export data.
func NewReader(r io.Reader) (io.Reader, error) {
buf := bufio.NewReader(r)
size, err := gcimporter.FindExportData(buf)
if err != nil {
return nil, err
}
// We were given an archive and found the __.PKGDEF in it.
// This tells us the size of the export data, and we don't
// need to return the entire file.
return &io.LimitedReader{
R: buf,
N: size,
}, nil
}
// readAll works the same way as io.ReadAll, but avoids allocations and copies
// by preallocating a byte slice of the necessary size if the size is known up
// front. This is always possible when the input is an archive. In that case,
// NewReader will return the known size using an io.LimitedReader.
func readAll(r io.Reader) ([]byte, error) {
if lr, ok := r.(*io.LimitedReader); ok {
data := make([]byte, lr.N)
_, err := io.ReadFull(lr, data)
return data, err
}
return io.ReadAll(r)
}
// Read reads export data from in, decodes it, and returns type
// information for the package.
//
// Read is capable of reading export data produced by [Write] at the
// same source code version, or by the last two Go releases (plus tip)
// of the standard Go compiler. Reading files from older compilers may
// produce an error.
//
// The package path (effectively its linker symbol prefix) is
// specified by path, since unlike the package name, this information
// may not be recorded in the export data.
//
// File position information is added to fset.
//
// Read may inspect and add to the imports map to ensure that references
// within the export data to other packages are consistent. The caller
// must ensure that imports[path] does not exist, or exists but is
// incomplete (see types.Package.Complete), and Read inserts the
// resulting package into this map entry.
//
// On return, the state of the reader is undefined.
func Read(in io.Reader, fset *token.FileSet, imports map[string]*types.Package, path string) (*types.Package, error) {
data, err := readAll(in)
if err != nil {
return nil, fmt.Errorf("reading export data for %q: %v", path, err)
}
if bytes.HasPrefix(data, []byte("!")) {
return nil, fmt.Errorf("can't read export data for %q directly from an archive file (call gcexportdata.NewReader first to extract export data)", path)
}
// The indexed export format starts with an 'i'; the older
// binary export format starts with a 'c', 'd', or 'v'
// (from "version"). Select appropriate importer.
if len(data) > 0 {
switch data[0] {
case 'v', 'c', 'd':
// binary, produced by cmd/compile till go1.10
return nil, fmt.Errorf("binary (%c) import format is no longer supported", data[0])
case 'i':
// indexed, produced by cmd/compile till go1.19,
// and also by [Write].
//
// If proposal #69491 is accepted, go/types
// serialization will be implemented by
// types.Export, to which Write would eventually
// delegate (explicitly dropping any pretence at
// inter-version Write-Read compatibility).
// This [Read] function would delegate to types.Import
// when it detects that the file was produced by Export.
_, pkg, err := gcimporter.IImportData(fset, imports, data[1:], path)
return pkg, err
case 'u':
// unified, produced by cmd/compile since go1.20
_, pkg, err := gcimporter.UImportData(fset, imports, data[1:], path)
return pkg, err
default:
l := min(len(data), 10)
return nil, fmt.Errorf("unexpected export data with prefix %q for path %s", string(data[:l]), path)
}
}
return nil, fmt.Errorf("empty export data for %s", path)
}
// Write writes encoded type information for the specified package to out.
// The FileSet provides file position information for named objects.
func Write(out io.Writer, fset *token.FileSet, pkg *types.Package) error {
if _, err := io.WriteString(out, "i"); err != nil {
return err
}
return gcimporter.IExportData(out, fset, pkg)
}
// ReadBundle reads an export bundle from in, decodes it, and returns type
// information for the packages.
// File position information is added to fset.
//
// ReadBundle may inspect and add to the imports map to ensure that references
// within the export bundle to other packages are consistent.
//
// On return, the state of the reader is undefined.
//
// Experimental: This API is experimental and may change in the future.
func ReadBundle(in io.Reader, fset *token.FileSet, imports map[string]*types.Package) ([]*types.Package, error) {
data, err := readAll(in)
if err != nil {
return nil, fmt.Errorf("reading export bundle: %v", err)
}
return gcimporter.IImportBundle(fset, imports, data)
}
// WriteBundle writes encoded type information for the specified packages to out.
// The FileSet provides file position information for named objects.
//
// Experimental: This API is experimental and may change in the future.
func WriteBundle(out io.Writer, fset *token.FileSet, pkgs []*types.Package) error {
return gcimporter.IExportBundle(out, fset, pkgs)
}
================================================
FILE: go/gcexportdata/importer.go
================================================
// Copyright 2016 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package gcexportdata
import (
"fmt"
"go/token"
"go/types"
"os"
)
// NewImporter returns a new instance of the types.Importer interface
// that reads type information from export data files written by gc.
// The Importer also satisfies types.ImporterFrom.
//
// Export data files are located using "go build" workspace conventions
// and the build.Default context.
//
// Use this importer instead of go/importer.For("gc", ...) to avoid the
// version-skew problems described in the documentation of this package,
// or to control the FileSet or access the imports map populated during
// package loading.
//
// Deprecated: Use the higher-level API in golang.org/x/tools/go/packages,
// which is more efficient.
func NewImporter(fset *token.FileSet, imports map[string]*types.Package) types.ImporterFrom {
return importer{fset, imports}
}
type importer struct {
fset *token.FileSet
imports map[string]*types.Package
}
func (imp importer) Import(importPath string) (*types.Package, error) {
return imp.ImportFrom(importPath, "", 0)
}
func (imp importer) ImportFrom(importPath, srcDir string, mode types.ImportMode) (_ *types.Package, err error) {
filename, path := Find(importPath, srcDir)
if filename == "" {
if importPath == "unsafe" {
// Even for unsafe, call Find first in case
// the package was vendored.
return types.Unsafe, nil
}
return nil, fmt.Errorf("can't find import: %s", importPath)
}
if pkg, ok := imp.imports[path]; ok && pkg.Complete() {
return pkg, nil // cache hit
}
// open file
f, err := os.Open(filename)
if err != nil {
return nil, err
}
defer func() {
f.Close()
if err != nil {
// add file name to error
err = fmt.Errorf("reading export data: %s: %v", filename, err)
}
}()
r, err := NewReader(f)
if err != nil {
return nil, err
}
return Read(r, imp.fset, imp.imports, path)
}
================================================
FILE: go/gcexportdata/main.go
================================================
// Copyright 2017 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build ignore
// The gcexportdata command is a diagnostic tool that displays the
// contents of gc export data files.
package main
import (
"flag"
"fmt"
"go/token"
"go/types"
"log"
"os"
"golang.org/x/tools/go/gcexportdata"
"golang.org/x/tools/go/types/typeutil"
)
var packageFlag = flag.String("package", "", "alternative package to print")
func main() {
log.SetPrefix("gcexportdata: ")
log.SetFlags(0)
flag.Usage = func() {
fmt.Fprintln(os.Stderr, "usage: gcexportdata [-package path] file.a")
}
flag.Parse()
if flag.NArg() != 1 {
flag.Usage()
os.Exit(2)
}
filename := flag.Args()[0]
f, err := os.Open(filename)
if err != nil {
log.Fatal(err)
}
r, err := gcexportdata.NewReader(f)
if err != nil {
log.Fatalf("%s: %s", filename, err)
}
// Decode the package.
const primary = ""
imports := make(map[string]*types.Package)
fset := token.NewFileSet()
pkg, err := gcexportdata.Read(r, fset, imports, primary)
if err != nil {
log.Fatalf("%s: %s", filename, err)
}
// Optionally select an indirectly mentioned package.
if *packageFlag != "" {
pkg = imports[*packageFlag]
if pkg == nil {
fmt.Fprintf(os.Stderr, "export data file %s does not mention %s; has:\n",
filename, *packageFlag)
for p := range imports {
if p != primary {
fmt.Fprintf(os.Stderr, "\t%s\n", p)
}
}
os.Exit(1)
}
}
// Print all package-level declarations, including non-exported ones.
fmt.Printf("package %s\n", pkg.Name())
for _, imp := range pkg.Imports() {
fmt.Printf("import %q\n", imp.Path())
}
qual := func(p *types.Package) string {
if pkg == p {
return ""
}
return p.Name()
}
scope := pkg.Scope()
for _, name := range scope.Names() {
obj := scope.Lookup(name)
fmt.Printf("%s: %s\n",
fset.Position(obj.Pos()),
types.ObjectString(obj, qual))
// For types, print each method.
if _, ok := obj.(*types.TypeName); ok {
for _, method := range typeutil.IntuitiveMethodSet(obj.Type(), nil) {
fmt.Printf("%s: %s\n",
fset.Position(method.Obj().Pos()),
types.SelectionString(method, qual))
}
}
}
}
================================================
FILE: go/internal/cgo/cgo.go
================================================
// Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package cgo handles cgo preprocessing of files containing `import "C"`.
//
// DESIGN
//
// The approach taken is to run the cgo processor on the package's
// CgoFiles and parse the output, faking the filenames of the
// resulting ASTs so that the synthetic file containing the C types is
// called "C" (e.g. "~/go/src/net/C") and the preprocessed files
// have their original names (e.g. "~/go/src/net/cgo_unix.go"),
// not the names of the actual temporary files.
//
// The advantage of this approach is its fidelity to 'go build'. The
// downside is that the token.Position.Offset for each AST node is
// incorrect, being an offset within the temporary file. Line numbers
// should still be correct because of the //line comments.
//
// The logic of this file is mostly plundered from the 'go build'
// tool, which also invokes the cgo preprocessor.
//
//
// REJECTED ALTERNATIVE
//
// An alternative approach that we explored is to extend go/types'
// Importer mechanism to provide the identity of the importing package
// so that each time `import "C"` appears it resolves to a different
// synthetic package containing just the objects needed in that case.
// The loader would invoke cgo but parse only the cgo_types.go file
// defining the package-level objects, discarding the other files
// resulting from preprocessing.
//
// The benefit of this approach would have been that source-level
// syntax information would correspond exactly to the original cgo
// file, with no preprocessing involved, making source tools like
// godoc, guru, and eg happy. However, the approach was rejected
// due to the additional complexity it would impose on go/types. (It
// made for a beautiful demo, though.)
//
// cgo files, despite their *.go extension, are not legal Go source
// files per the specification since they may refer to unexported
// members of package "C" such as C.int. Also, a function such as
// C.getpwent has in effect two types, one matching its C type and one
// which additionally returns (errno C.int). The cgo preprocessor
// uses name mangling to distinguish these two functions in the
// processed code, but go/types would need to duplicate this logic in
// its handling of function calls, analogous to the treatment of map
// lookups in which y=m[k] and y,ok=m[k] are both legal.
package cgo
import (
"fmt"
"go/ast"
"go/build"
"go/parser"
"go/token"
"log"
"os"
"os/exec"
"path/filepath"
"regexp"
"strings"
)
// ProcessFiles invokes the cgo preprocessor on bp.CgoFiles, parses
// the output and returns the resulting ASTs.
func ProcessFiles(bp *build.Package, fset *token.FileSet, DisplayPath func(path string) string, mode parser.Mode) ([]*ast.File, error) {
tmpdir, err := os.MkdirTemp("", strings.Replace(bp.ImportPath, "/", "_", -1)+"_C")
if err != nil {
return nil, err
}
defer os.RemoveAll(tmpdir)
pkgdir := bp.Dir
if DisplayPath != nil {
pkgdir = DisplayPath(pkgdir)
}
cgoFiles, cgoDisplayFiles, err := Run(bp, pkgdir, tmpdir, false)
if err != nil {
return nil, err
}
var files []*ast.File
for i := range cgoFiles {
rd, err := os.Open(cgoFiles[i])
if err != nil {
return nil, err
}
display := filepath.Join(bp.Dir, cgoDisplayFiles[i])
f, err := parser.ParseFile(fset, display, rd, mode)
rd.Close()
if err != nil {
return nil, err
}
files = append(files, f)
}
return files, nil
}
var cgoRe = regexp.MustCompile(`[/\\:]`)
// Run invokes the cgo preprocessor on bp.CgoFiles and returns two
// lists of files: the resulting processed files (in temporary
// directory tmpdir) and the corresponding names of the unprocessed files.
//
// Run is adapted from (*builder).cgo in
// $GOROOT/src/cmd/go/build.go, but these features are unsupported:
// Objective C, CGOPKGPATH, CGO_FLAGS.
//
// If useabs is set to true, absolute paths of the bp.CgoFiles will be passed in
// to the cgo preprocessor. This in turn will set the // line comments
// referring to those files to use absolute paths. This is needed for
// go/packages using the legacy go list support so it is able to find
// the original files.
func Run(bp *build.Package, pkgdir, tmpdir string, useabs bool) (files, displayFiles []string, err error) {
cgoCPPFLAGS, _, _, _ := cflags(bp, true)
_, cgoexeCFLAGS, _, _ := cflags(bp, false)
if len(bp.CgoPkgConfig) > 0 {
pcCFLAGS, err := pkgConfigFlags(bp)
if err != nil {
return nil, nil, err
}
cgoCPPFLAGS = append(cgoCPPFLAGS, pcCFLAGS...)
}
// Allows including _cgo_export.h from .[ch] files in the package.
cgoCPPFLAGS = append(cgoCPPFLAGS, "-I", tmpdir)
// _cgo_gotypes.go (displayed "C") contains the type definitions.
files = append(files, filepath.Join(tmpdir, "_cgo_gotypes.go"))
displayFiles = append(displayFiles, "C")
for _, fn := range bp.CgoFiles {
// "foo.cgo1.go" (displayed "foo.go") is the processed Go source.
f := cgoRe.ReplaceAllString(fn[:len(fn)-len("go")], "_")
files = append(files, filepath.Join(tmpdir, f+"cgo1.go"))
displayFiles = append(displayFiles, fn)
}
var cgoflags []string
if bp.Goroot && bp.ImportPath == "runtime/cgo" {
cgoflags = append(cgoflags, "-import_runtime_cgo=false")
}
if bp.Goroot && bp.ImportPath == "runtime/race" || bp.ImportPath == "runtime/cgo" {
cgoflags = append(cgoflags, "-import_syscall=false")
}
var cgoFiles []string = bp.CgoFiles
if useabs {
cgoFiles = make([]string, len(bp.CgoFiles))
for i := range cgoFiles {
cgoFiles[i] = filepath.Join(pkgdir, bp.CgoFiles[i])
}
}
args := stringList(
"go", "tool", "cgo", "-objdir", tmpdir, cgoflags, "--",
cgoCPPFLAGS, cgoexeCFLAGS, cgoFiles,
)
if false {
log.Printf("Running cgo for package %q: %s (dir=%s)", bp.ImportPath, args, pkgdir)
}
cmd := exec.Command(args[0], args[1:]...)
cmd.Dir = pkgdir
cmd.Env = append(os.Environ(), "PWD="+pkgdir)
cmd.Stdout = os.Stderr
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil {
return nil, nil, fmt.Errorf("cgo failed: %s: %s", args, err)
}
return files, displayFiles, nil
}
// -- unmodified from 'go build' ---------------------------------------
// Return the flags to use when invoking the C or C++ compilers, or cgo.
func cflags(p *build.Package, def bool) (cppflags, cflags, cxxflags, ldflags []string) {
var defaults string
if def {
defaults = "-g -O2"
}
cppflags = stringList(envList("CGO_CPPFLAGS", ""), p.CgoCPPFLAGS)
cflags = stringList(envList("CGO_CFLAGS", defaults), p.CgoCFLAGS)
cxxflags = stringList(envList("CGO_CXXFLAGS", defaults), p.CgoCXXFLAGS)
ldflags = stringList(envList("CGO_LDFLAGS", defaults), p.CgoLDFLAGS)
return
}
// envList returns the value of the given environment variable broken
// into fields, using the default value when the variable is empty.
func envList(key, def string) []string {
v := os.Getenv(key)
if v == "" {
v = def
}
return strings.Fields(v)
}
// stringList's arguments should be a sequence of string or []string values.
// stringList flattens them into a single []string.
func stringList(args ...any) []string {
var x []string
for _, arg := range args {
switch arg := arg.(type) {
case []string:
x = append(x, arg...)
case string:
x = append(x, arg)
default:
panic("stringList: invalid argument")
}
}
return x
}
================================================
FILE: go/internal/cgo/cgo_pkgconfig.go
================================================
// Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package cgo
import (
"errors"
"fmt"
"go/build"
"os/exec"
"strings"
)
// pkgConfig runs pkg-config with the specified arguments and returns the flags it prints.
func pkgConfig(mode string, pkgs []string) (flags []string, err error) {
cmd := exec.Command("pkg-config", append([]string{mode}, pkgs...)...)
out, err := cmd.Output()
if err != nil {
s := fmt.Sprintf("%s failed: %v", strings.Join(cmd.Args, " "), err)
if len(out) > 0 {
s = fmt.Sprintf("%s: %s", s, out)
}
if err, ok := err.(*exec.ExitError); ok && len(err.Stderr) > 0 {
s = fmt.Sprintf("%s\nstderr:\n%s", s, err.Stderr)
}
return nil, errors.New(s)
}
if len(out) > 0 {
flags = strings.Fields(string(out))
}
return
}
// pkgConfigFlags calls pkg-config if needed and returns the cflags
// needed to build the package.
func pkgConfigFlags(p *build.Package) (cflags []string, err error) {
if len(p.CgoPkgConfig) == 0 {
return nil, nil
}
return pkgConfig("--cflags", p.CgoPkgConfig)
}
================================================
FILE: go/internal/gccgoimporter/ar.go
================================================
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Except for this comment, this file is a verbatim copy of the file
// with the same name in $GOROOT/src/go/internal/gccgoimporter.
package gccgoimporter
import (
"bytes"
"debug/elf"
"errors"
"fmt"
"io"
"strconv"
"strings"
)
// Magic strings for different archive file formats.
const (
armag = "!\n"
armagt = "!\n"
armagb = "\n"
)
// Offsets and sizes for fields in a standard archive header.
const (
arNameOff = 0
arNameSize = 16
arDateOff = arNameOff + arNameSize
arDateSize = 12
arUIDOff = arDateOff + arDateSize
arUIDSize = 6
arGIDOff = arUIDOff + arUIDSize
arGIDSize = 6
arModeOff = arGIDOff + arGIDSize
arModeSize = 8
arSizeOff = arModeOff + arModeSize
arSizeSize = 10
arFmagOff = arSizeOff + arSizeSize
arFmagSize = 2
arHdrSize = arFmagOff + arFmagSize
)
// The contents of the fmag field of a standard archive header.
const arfmag = "`\n"
// arExportData takes an archive file and returns a ReadSeeker for the
// export data in that file. This assumes that there is only one
// object in the archive containing export data, which is not quite
// what gccgo does; gccgo concatenates together all the export data
// for all the objects in the file. In practice that case does not arise.
func arExportData(archive io.ReadSeeker) (io.ReadSeeker, error) {
if _, err := archive.Seek(0, io.SeekStart); err != nil {
return nil, err
}
var buf [len(armag)]byte
if _, err := archive.Read(buf[:]); err != nil {
return nil, err
}
switch string(buf[:]) {
case armag:
return standardArExportData(archive)
case armagt:
return nil, errors.New("unsupported thin archive")
case armagb:
return nil, errors.New("unsupported AIX big archive")
default:
return nil, fmt.Errorf("unrecognized archive file format %q", buf[:])
}
}
// standardArExportData returns export data form a standard archive.
func standardArExportData(archive io.ReadSeeker) (io.ReadSeeker, error) {
off := int64(len(armag))
for {
var hdrBuf [arHdrSize]byte
if _, err := archive.Read(hdrBuf[:]); err != nil {
return nil, err
}
off += arHdrSize
if !bytes.Equal(hdrBuf[arFmagOff:arFmagOff+arFmagSize], []byte(arfmag)) {
return nil, fmt.Errorf("archive header format header (%q)", hdrBuf[:])
}
size, err := strconv.ParseInt(strings.TrimSpace(string(hdrBuf[arSizeOff:arSizeOff+arSizeSize])), 10, 64)
if err != nil {
return nil, fmt.Errorf("error parsing size in archive header (%q): %v", hdrBuf[:], err)
}
fn := hdrBuf[arNameOff : arNameOff+arNameSize]
if fn[0] == '/' && (fn[1] == ' ' || fn[1] == '/' || bytes.Equal(fn[:8], []byte("/SYM64/ "))) {
// Archive symbol table or extended name table,
// which we don't care about.
} else {
archiveAt := readerAtFromSeeker(archive)
ret, err := elfFromAr(io.NewSectionReader(archiveAt, off, size))
if ret != nil || err != nil {
return ret, err
}
}
if size&1 != 0 {
size++
}
off += size
if _, err := archive.Seek(off, io.SeekStart); err != nil {
return nil, err
}
}
}
// elfFromAr tries to get export data from an archive member as an ELF file.
// If there is no export data, this returns nil, nil.
func elfFromAr(member *io.SectionReader) (io.ReadSeeker, error) {
ef, err := elf.NewFile(member)
if err != nil {
return nil, err
}
sec := ef.Section(".go_export")
if sec == nil {
return nil, nil
}
return sec.Open(), nil
}
// readerAtFromSeeker turns an io.ReadSeeker into an io.ReaderAt.
// This is only safe because there won't be any concurrent seeks
// while this code is executing.
func readerAtFromSeeker(rs io.ReadSeeker) io.ReaderAt {
if ret, ok := rs.(io.ReaderAt); ok {
return ret
}
return seekerReadAt{rs}
}
type seekerReadAt struct {
seeker io.ReadSeeker
}
func (sra seekerReadAt) ReadAt(p []byte, off int64) (int, error) {
if _, err := sra.seeker.Seek(off, io.SeekStart); err != nil {
return 0, err
}
return sra.seeker.Read(p)
}
================================================
FILE: go/internal/gccgoimporter/backdoor.go
================================================
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// This file opens a back door to the parser for golang.org/x/tools/go/gccgoexportdata.
package gccgoimporter
import (
"go/types"
"io"
)
// Parse reads and parses gccgo export data from in and constructs a
// Package, inserting it into the imports map.
func Parse(in io.Reader, imports map[string]*types.Package, path string) (_ *types.Package, err error) {
var p parser
p.init(path, in, imports)
defer func() {
switch x := recover().(type) {
case nil:
// success
case importError:
err = x
default:
panic(x) // resume unexpected panic
}
}()
pkg := p.parsePackage()
imports[path] = pkg
return pkg, err
}
================================================
FILE: go/internal/gccgoimporter/gccgoinstallation.go
================================================
// Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Except for this comment, this file is a verbatim copy of the file
// with the same name in $GOROOT/src/go/internal/gccgoimporter.
package gccgoimporter
import (
"bufio"
"go/types"
"os"
"os/exec"
"path/filepath"
"strings"
)
// Information about a specific installation of gccgo.
type GccgoInstallation struct {
// Version of gcc (e.g. 4.8.0).
GccVersion string
// Target triple (e.g. x86_64-unknown-linux-gnu).
TargetTriple string
// Built-in library paths used by this installation.
LibPaths []string
}
// Ask the driver at the given path for information for this GccgoInstallation.
// The given arguments are passed directly to the call of the driver.
func (inst *GccgoInstallation) InitFromDriver(gccgoPath string, args ...string) (err error) {
argv := append([]string{"-###", "-S", "-x", "go", "-"}, args...)
cmd := exec.Command(gccgoPath, argv...)
stderr, err := cmd.StderrPipe()
if err != nil {
return
}
err = cmd.Start()
if err != nil {
return
}
scanner := bufio.NewScanner(stderr)
for scanner.Scan() {
line := scanner.Text()
switch {
case strings.HasPrefix(line, "Target: "):
inst.TargetTriple = line[8:]
case line[0] == ' ':
args := strings.Fields(line)
for _, arg := range args[1:] {
if strings.HasPrefix(arg, "-L") {
inst.LibPaths = append(inst.LibPaths, arg[2:])
}
}
}
}
argv = append([]string{"-dumpversion"}, args...)
stdout, err := exec.Command(gccgoPath, argv...).Output()
if err != nil {
return
}
inst.GccVersion = strings.TrimSpace(string(stdout))
return
}
// Return the list of export search paths for this GccgoInstallation.
func (inst *GccgoInstallation) SearchPaths() (paths []string) {
for _, lpath := range inst.LibPaths {
spath := filepath.Join(lpath, "go", inst.GccVersion)
fi, err := os.Stat(spath)
if err != nil || !fi.IsDir() {
continue
}
paths = append(paths, spath)
spath = filepath.Join(spath, inst.TargetTriple)
fi, err = os.Stat(spath)
if err != nil || !fi.IsDir() {
continue
}
paths = append(paths, spath)
}
paths = append(paths, inst.LibPaths...)
return
}
// Return an importer that searches incpaths followed by the gcc installation's
// built-in search paths and the current directory.
func (inst *GccgoInstallation) GetImporter(incpaths []string, initmap map[*types.Package]InitData) Importer {
return GetImporter(append(append(incpaths, inst.SearchPaths()...), "."), initmap)
}
================================================
FILE: go/internal/gccgoimporter/gccgoinstallation_test.go
================================================
// Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Except for this comment, this file is a verbatim copy of the file
// with the same name in $GOROOT/src/go/internal/gccgoimporter.
package gccgoimporter
import (
"go/types"
"runtime"
"testing"
)
// importablePackages is a list of packages that we verify that we can
// import. This should be all standard library packages in all relevant
// versions of gccgo. Note that since gccgo follows a different release
// cycle, and since different systems have different versions installed,
// we can't use the last-two-versions rule of the gc toolchain.
var importablePackages = [...]string{
"archive/tar",
"archive/zip",
"bufio",
"bytes",
"compress/bzip2",
"compress/flate",
"compress/gzip",
"compress/lzw",
"compress/zlib",
"container/heap",
"container/list",
"container/ring",
"crypto/aes",
"crypto/cipher",
"crypto/des",
"crypto/dsa",
"crypto/ecdsa",
"crypto/elliptic",
"crypto",
"crypto/hmac",
"crypto/md5",
"crypto/rand",
"crypto/rc4",
"crypto/rsa",
"crypto/sha1",
"crypto/sha256",
"crypto/sha512",
"crypto/subtle",
"crypto/tls",
"crypto/x509",
"crypto/x509/pkix",
"database/sql/driver",
"database/sql",
"debug/dwarf",
"debug/elf",
"debug/gosym",
"debug/macho",
"debug/pe",
"encoding/ascii85",
"encoding/asn1",
"encoding/base32",
"encoding/base64",
"encoding/binary",
"encoding/csv",
"encoding/gob",
// "encoding", // Added in GCC 4.9.
"encoding/hex",
"encoding/json",
"encoding/pem",
"encoding/xml",
"errors",
"expvar",
"flag",
"fmt",
"go/ast",
"go/build",
"go/doc",
// "go/format", // Added in GCC 4.8.
"go/parser",
"go/printer",
"go/scanner",
"go/token",
"hash/adler32",
"hash/crc32",
"hash/crc64",
"hash/fnv",
"hash",
"html",
"html/template",
"image/color",
// "image/color/palette", // Added in GCC 4.9.
"image/draw",
"image/gif",
"image",
"image/jpeg",
"image/png",
"index/suffixarray",
"io",
"io/ioutil",
"log",
"log/syslog",
"math/big",
"math/cmplx",
"math",
"math/rand",
"mime",
"mime/multipart",
"net",
"net/http/cgi",
// "net/http/cookiejar", // Added in GCC 4.8.
"net/http/fcgi",
"net/http",
"net/http/httptest",
"net/http/httputil",
"net/http/pprof",
"net/mail",
"net/rpc",
"net/rpc/jsonrpc",
"net/smtp",
"net/textproto",
"net/url",
"os/exec",
"os",
"os/signal",
"os/user",
"path/filepath",
"path",
"reflect",
"regexp",
"regexp/syntax",
"runtime/debug",
"runtime",
"runtime/pprof",
"sort",
"strconv",
"strings",
"sync/atomic",
"sync",
"syscall",
"testing",
"testing/iotest",
"testing/quick",
"text/scanner",
"text/tabwriter",
"text/template",
"text/template/parse",
"time",
"unicode",
"unicode/utf16",
"unicode/utf8",
}
func TestInstallationImporter(t *testing.T) {
// This test relies on gccgo being around.
gpath := gccgoPath()
if gpath == "" {
t.Skip("This test needs gccgo")
}
if runtime.GOOS == "aix" {
// We don't yet have a debug/xcoff package for reading
// object files on AIX. Remove this skip if/when issue #29038
// is implemented (see also issue #49445).
t.Skip("no support yet for debug/xcoff")
}
var inst GccgoInstallation
err := inst.InitFromDriver(gpath)
if err != nil {
t.Fatal(err)
}
imp := inst.GetImporter(nil, nil)
// Ensure we don't regress the number of packages we can parse. First import
// all packages into the same map and then each individually.
pkgMap := make(map[string]*types.Package)
for _, pkg := range importablePackages {
_, err = imp(pkgMap, pkg, ".", nil)
if err != nil {
t.Error(err)
}
}
for _, pkg := range importablePackages {
_, err = imp(make(map[string]*types.Package), pkg, ".", nil)
if err != nil {
t.Error(err)
}
}
// Test for certain specific entities in the imported data.
for _, test := range [...]importerTest{
{pkgpath: "io", name: "Reader", want: "type Reader interface{Read(p []byte) (n int, err error)}"},
{pkgpath: "io", name: "ReadWriter", want: "type ReadWriter interface{Reader; Writer}"},
{pkgpath: "math", name: "Pi", want: "const Pi untyped float"},
{pkgpath: "math", name: "Sin", want: "func Sin(x float64) float64"},
{pkgpath: "sort", name: "Search", want: "func Search(n int, f func(int) bool) int"},
{pkgpath: "unsafe", name: "Pointer", want: "type Pointer"},
} {
runImporterTest(t, imp, nil, &test)
}
}
================================================
FILE: go/internal/gccgoimporter/importer.go
================================================
// Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Except for this comment and the import path, this file is a verbatim copy of the file
// with the same name in $GOROOT/src/go/internal/gccgoimporter.
// Package gccgoimporter implements Import for gccgo-generated object files.
package gccgoimporter // import "golang.org/x/tools/go/internal/gccgoimporter"
import (
"debug/elf"
"fmt"
"go/types"
"io"
"os"
"path/filepath"
"strings"
)
// A PackageInit describes an imported package that needs initialization.
type PackageInit struct {
Name string // short package name
InitFunc string // name of init function
Priority int // priority of init function, see InitData.Priority
}
// The gccgo-specific init data for a package.
type InitData struct {
// Initialization priority of this package relative to other packages.
// This is based on the maximum depth of the package's dependency graph;
// it is guaranteed to be greater than that of its dependencies.
Priority int
// The list of packages which this package depends on to be initialized,
// including itself if needed. This is the subset of the transitive closure of
// the package's dependencies that need initialization.
Inits []PackageInit
}
// Locate the file from which to read export data.
// This is intended to replicate the logic in gofrontend.
func findExportFile(searchpaths []string, pkgpath string) (string, error) {
for _, spath := range searchpaths {
pkgfullpath := filepath.Join(spath, pkgpath)
pkgdir, name := filepath.Split(pkgfullpath)
for _, filepath := range [...]string{
pkgfullpath,
pkgfullpath + ".gox",
pkgdir + "lib" + name + ".so",
pkgdir + "lib" + name + ".a",
pkgfullpath + ".o",
} {
fi, err := os.Stat(filepath)
if err == nil && !fi.IsDir() {
return filepath, nil
}
}
}
return "", fmt.Errorf("%s: could not find export data (tried %s)", pkgpath, strings.Join(searchpaths, ":"))
}
const (
gccgov1Magic = "v1;\n"
gccgov2Magic = "v2;\n"
gccgov3Magic = "v3;\n"
goimporterMagic = "\n$$ "
archiveMagic = "!"
// Take name from Name method (like on os.File) if present.
if n, ok := rc.(interface{ Name() string }); ok {
fpath = n.Name()
}
} else {
fpath, err = findExportFile(searchpaths, pkgpath)
if err != nil {
return nil, err
}
r, closer, err := openExportFile(fpath)
if err != nil {
return nil, err
}
if closer != nil {
defer closer.Close()
}
reader = r
}
var magics string
magics, err = readMagic(reader)
if err != nil {
return
}
if magics == archiveMagic {
reader, err = arExportData(reader)
if err != nil {
return
}
magics, err = readMagic(reader)
if err != nil {
return
}
}
switch magics {
case gccgov1Magic, gccgov2Magic, gccgov3Magic:
var p parser
p.init(fpath, reader, imports)
pkg = p.parsePackage()
if initmap != nil {
initmap[pkg] = p.initdata
}
// Excluded for now: Standard gccgo doesn't support this import format currently.
// case goimporterMagic:
// var data []byte
// data, err = io.ReadAll(reader)
// if err != nil {
// return
// }
// var n int
// n, pkg, err = importer.ImportData(imports, data)
// if err != nil {
// return
// }
// if initmap != nil {
// suffixreader := bytes.NewReader(data[n:])
// var p parser
// p.init(fpath, suffixreader, nil)
// p.parseInitData()
// initmap[pkg] = p.initdata
// }
default:
err = fmt.Errorf("unrecognized magic string: %q", magics)
}
return
}
}
// readMagic reads the four bytes at the start of a ReadSeeker and
// returns them as a string.
func readMagic(reader io.ReadSeeker) (string, error) {
var magic [4]byte
if _, err := reader.Read(magic[:]); err != nil {
return "", err
}
if _, err := reader.Seek(0, io.SeekStart); err != nil {
return "", err
}
return string(magic[:]), nil
}
================================================
FILE: go/internal/gccgoimporter/importer_test.go
================================================
// Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Except for this comment, this file is a verbatim copy of the file
// with the same name in $GOROOT/src/go/internal/gccgoimporter without
// the import of "testenv".
package gccgoimporter
import (
"go/types"
"os"
"os/exec"
"path/filepath"
"regexp"
"runtime"
"strconv"
"testing"
)
type importerTest struct {
pkgpath, name, want, wantval string
wantinits []string
gccgoVersion int // minimum gccgo version (0 => any)
}
func runImporterTest(t *testing.T, imp Importer, initmap map[*types.Package]InitData, test *importerTest) {
pkg, err := imp(make(map[string]*types.Package), test.pkgpath, ".", nil)
if err != nil {
t.Error(err)
return
}
if test.name != "" {
obj := pkg.Scope().Lookup(test.name)
if obj == nil {
t.Errorf("%s: object not found", test.name)
return
}
got := types.ObjectString(obj, types.RelativeTo(pkg))
if got != test.want {
t.Errorf("%s: got %q; want %q", test.name, got, test.want)
}
if test.wantval != "" {
gotval := obj.(*types.Const).Val().String()
if gotval != test.wantval {
t.Errorf("%s: got val %q; want val %q", test.name, gotval, test.wantval)
}
}
}
if len(test.wantinits) > 0 {
initdata := initmap[pkg]
found := false
// Check that the package's own init function has the package's priority
for _, pkginit := range initdata.Inits {
if pkginit.InitFunc == test.wantinits[0] {
found = true
break
}
}
if !found {
t.Errorf("%s: could not find expected function %q", test.pkgpath, test.wantinits[0])
}
// FIXME: the original version of this test was written against
// the v1 export data scheme for capturing init functions, so it
// verified the priority values. We moved away from the priority
// scheme some time ago; it is not clear how much work it would be
// to validate the new init export data.
}
}
// When adding tests to this list, be sure to set the 'gccgoVersion'
// field if the testcases uses a "recent" Go addition (ex: aliases).
var importerTests = [...]importerTest{
{pkgpath: "pointer", name: "Int8Ptr", want: "type Int8Ptr *int8"},
{pkgpath: "complexnums", name: "NN", want: "const NN untyped complex", wantval: "(-1 + -1i)"},
{pkgpath: "complexnums", name: "NP", want: "const NP untyped complex", wantval: "(-1 + 1i)"},
{pkgpath: "complexnums", name: "PN", want: "const PN untyped complex", wantval: "(1 + -1i)"},
{pkgpath: "complexnums", name: "PP", want: "const PP untyped complex", wantval: "(1 + 1i)"},
{pkgpath: "conversions", name: "Bits", want: "const Bits Units", wantval: `"bits"`},
{pkgpath: "time", name: "Duration", want: "type Duration int64"},
{pkgpath: "time", name: "Nanosecond", want: "const Nanosecond Duration", wantval: "1"},
{pkgpath: "unicode", name: "IsUpper", want: "func IsUpper(r rune) bool"},
{pkgpath: "unicode", name: "MaxRune", want: "const MaxRune untyped rune", wantval: "1114111"},
{pkgpath: "imports", wantinits: []string{"imports..import", "fmt..import"}},
{pkgpath: "importsar", name: "Hello", want: "var Hello string"},
{pkgpath: "aliases", name: "A14", gccgoVersion: 7, want: "type A14 = func(int, T0) chan T2"},
{pkgpath: "aliases", name: "C0", gccgoVersion: 7, want: "type C0 struct{f1 C1; f2 C1}"},
{pkgpath: "escapeinfo", name: "NewT", want: "func NewT(data []byte) *T"},
{pkgpath: "issue27856", name: "M", gccgoVersion: 7, want: "type M struct{E F}"},
{pkgpath: "v1reflect", name: "Type", want: "type Type interface{Align() int; AssignableTo(u Type) bool; Bits() int; ChanDir() ChanDir; Elem() Type; Field(i int) StructField; FieldAlign() int; FieldByIndex(index []int) StructField; FieldByName(name string) (StructField, bool); FieldByNameFunc(match func(string) bool) (StructField, bool); Implements(u Type) bool; In(i int) Type; IsVariadic() bool; Key() Type; Kind() Kind; Len() int; Method(int) Method; MethodByName(string) (Method, bool); Name() string; NumField() int; NumIn() int; NumMethod() int; NumOut() int; Out(i int) Type; PkgPath() string; Size() uintptr; String() string; common() *commonType; rawString() string; runtimeType() *runtimeType; uncommon() *uncommonType}"},
{pkgpath: "nointerface", name: "I", want: "type I int"},
{pkgpath: "issue29198", name: "FooServer", gccgoVersion: 7, want: "type FooServer struct{FooServer *FooServer; user string; ctx context.Context}"},
{pkgpath: "issue30628", name: "Apple", want: "type Apple struct{hey sync.RWMutex; x int; RQ [517]struct{Count uintptr; NumBytes uintptr; Last uintptr}}"},
{pkgpath: "issue31540", name: "S", gccgoVersion: 7, want: "type S struct{b int; map[Y]Z}"},
{pkgpath: "issue34182", name: "T1", want: "type T1 struct{f *T2}"},
{pkgpath: "notinheap", name: "S", want: "type S struct{}"},
}
func TestGoxImporter(t *testing.T) {
testenv.MustHaveExec(t) // this is to skip nacl, js
initmap := make(map[*types.Package]InitData)
imp := GetImporter([]string{"testdata"}, initmap)
for _, test := range importerTests {
runImporterTest(t, imp, initmap, &test)
}
}
// gccgoPath returns a path to gccgo if it is present (either in
// path or specified via GCCGO environment variable), or an
// empty string if no gccgo is available.
func gccgoPath() string {
gccgoname := os.Getenv("GCCGO")
if gccgoname == "" {
gccgoname = "gccgo"
}
if gpath, gerr := exec.LookPath(gccgoname); gerr == nil {
return gpath
}
return ""
}
func TestObjImporter(t *testing.T) {
// This test relies on gccgo being around.
gpath := gccgoPath()
if gpath == "" {
t.Skip("This test needs gccgo")
}
if runtime.GOOS == "aix" {
// We don't yet have a debug/xcoff package for reading
// object files on AIX. Remove this skip if/when issue #29038
// is implemented (see also issue #49445).
t.Skip("no support yet for debug/xcoff")
}
verout, err := exec.Command(gpath, "--version").Output()
if err != nil {
t.Logf("%s", verout)
if exit, ok := err.(*exec.ExitError); ok && len(exit.Stderr) > 0 {
t.Logf("stderr:\n%s", exit.Stderr)
}
t.Fatal(err)
}
vers := regexp.MustCompile(`([0-9]+)\.([0-9]+)`).FindSubmatch(verout)
if len(vers) == 0 {
t.Fatalf("could not find version number in %s", verout)
}
major, err := strconv.Atoi(string(vers[1]))
if err != nil {
t.Fatal(err)
}
minor, err := strconv.Atoi(string(vers[2]))
if err != nil {
t.Fatal(err)
}
t.Logf("gccgo version %d.%d", major, minor)
tmpdir := t.TempDir()
initmap := make(map[*types.Package]InitData)
imp := GetImporter([]string{tmpdir}, initmap)
artmpdir := t.TempDir()
arinitmap := make(map[*types.Package]InitData)
arimp := GetImporter([]string{artmpdir}, arinitmap)
for _, test := range importerTests {
if major < test.gccgoVersion {
// Support for type aliases was added in GCC 7.
t.Logf("skipping %q: not supported before gccgo version %d", test.pkgpath, test.gccgoVersion)
continue
}
gofile := filepath.Join("testdata", test.pkgpath+".go")
if _, err := os.Stat(gofile); os.IsNotExist(err) {
continue
}
ofile := filepath.Join(tmpdir, test.pkgpath+".o")
afile := filepath.Join(artmpdir, "lib"+test.pkgpath+".a")
cmd := exec.Command(gpath, "-fgo-pkgpath="+test.pkgpath, "-c", "-o", ofile, gofile)
if out, err := cmd.CombinedOutput(); err != nil {
t.Logf("%s", out)
t.Fatalf("gccgo %s failed: %s", gofile, err)
}
runImporterTest(t, imp, initmap, &test)
cmd = exec.Command("ar", "cr", afile, ofile)
if out, err := cmd.CombinedOutput(); err != nil {
t.Logf("%s", out)
t.Fatalf("ar cr %s %s failed: %s", afile, ofile, err)
}
runImporterTest(t, arimp, arinitmap, &test)
if err = os.Remove(ofile); err != nil {
t.Fatal(err)
}
if err = os.Remove(afile); err != nil {
t.Fatal(err)
}
}
}
================================================
FILE: go/internal/gccgoimporter/newInterface10.go
================================================
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build !go1.11
package gccgoimporter
import "go/types"
func newInterface(methods []*types.Func, embeddeds []types.Type) *types.Interface {
named := make([]*types.Named, len(embeddeds))
for i, e := range embeddeds {
var ok bool
named[i], ok = e.(*types.Named)
if !ok {
panic("embedding of non-defined interfaces in interfaces is not supported before Go 1.11")
}
}
return types.NewInterface(methods, named)
}
================================================
FILE: go/internal/gccgoimporter/newInterface11.go
================================================
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build go1.11
package gccgoimporter
import "go/types"
func newInterface(methods []*types.Func, embeddeds []types.Type) *types.Interface {
return types.NewInterfaceType(methods, embeddeds)
}
================================================
FILE: go/internal/gccgoimporter/parser.go
================================================
// Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Except for this comment, this file is a verbatim copy of the file
// with the same name in $GOROOT/src/go/internal/gccgoimporter, with
// a small modification in parseInterface to support older Go versions.
package gccgoimporter
import (
"bytes"
"errors"
"fmt"
"go/constant"
"go/token"
"go/types"
"io"
"strconv"
"strings"
"text/scanner"
"unicode/utf8"
"golang.org/x/tools/internal/typesinternal"
)
type parser struct {
scanner *scanner.Scanner
version string // format version
tok rune // current token
lit string // literal string; only valid for Ident, Int, String tokens
pkgpath string // package path of imported package
pkgname string // name of imported package
pkg *types.Package // reference to imported package
imports map[string]*types.Package // package path -> package object
typeList []types.Type // type number -> type
typeData []string // unparsed type data (v3 and later)
fixups []fixupRecord // fixups to apply at end of parsing
initdata InitData // package init priority data
aliases map[int]string // maps saved type number to alias name
}
// When reading export data it's possible to encounter a defined type
// N1 with an underlying defined type N2 while we are still reading in
// that defined type N2; see issues #29006 and #29198 for instances
// of this. Example:
//
// type N1 N2
// type N2 struct {
// ...
// p *N1
// }
//
// To handle such cases, the parser generates a fixup record (below) and
// delays setting of N1's underlying type until parsing is complete, at
// which point fixups are applied.
type fixupRecord struct {
toUpdate *types.Named // type to modify when fixup is processed
target types.Type // type that was incomplete when fixup was created
}
func (p *parser) init(filename string, src io.Reader, imports map[string]*types.Package) {
p.scanner = new(scanner.Scanner)
p.initScanner(filename, src)
p.imports = imports
p.aliases = make(map[int]string)
p.typeList = make([]types.Type, 1 /* type numbers start at 1 */, 16)
}
func (p *parser) initScanner(filename string, src io.Reader) {
p.scanner.Init(src)
p.scanner.Error = func(_ *scanner.Scanner, msg string) { p.error(msg) }
p.scanner.Mode = scanner.ScanIdents | scanner.ScanInts | scanner.ScanFloats | scanner.ScanStrings
p.scanner.Whitespace = 1<<'\t' | 1<<' '
p.scanner.Filename = filename // for good error messages
p.next()
}
type importError struct {
pos scanner.Position
err error
}
func (e importError) Error() string {
return fmt.Sprintf("import error %s (byte offset = %d): %s", e.pos, e.pos.Offset, e.err)
}
func (p *parser) error(err any) {
if s, ok := err.(string); ok {
err = errors.New(s)
}
// panic with a runtime.Error if err is not an error
panic(importError{p.scanner.Pos(), err.(error)})
}
func (p *parser) errorf(format string, args ...any) {
p.error(fmt.Errorf(format, args...))
}
func (p *parser) expect(tok rune) string {
lit := p.lit
if p.tok != tok {
p.errorf("expected %s, got %s (%s)", scanner.TokenString(tok), scanner.TokenString(p.tok), lit)
}
p.next()
return lit
}
func (p *parser) expectEOL() {
if p.version == "v1" || p.version == "v2" {
p.expect(';')
}
p.expect('\n')
}
func (p *parser) expectKeyword(keyword string) {
lit := p.expect(scanner.Ident)
if lit != keyword {
p.errorf("expected keyword %s, got %q", keyword, lit)
}
}
func (p *parser) parseString() string {
str, err := strconv.Unquote(p.expect(scanner.String))
if err != nil {
p.error(err)
}
return str
}
// parseUnquotedString parses an UnquotedString:
//
// unquotedString = { unquotedStringChar } .
// unquotedStringChar = .
func (p *parser) parseUnquotedString() string {
if p.tok == scanner.EOF {
p.error("unexpected EOF")
}
var buf bytes.Buffer
buf.WriteString(p.scanner.TokenText())
// This loop needs to examine each character before deciding whether to consume it. If we see a semicolon,
// we need to let it be consumed by p.next().
for ch := p.scanner.Peek(); ch != '\n' && ch != ';' && ch != scanner.EOF && p.scanner.Whitespace&(1<" . (optional and ignored)
p.next()
p.expectKeyword("esc")
p.expect(':')
p.expect(scanner.Int)
p.expect('>')
}
if p.tok == '.' {
p.next()
p.expect('.')
p.expect('.')
isVariadic = true
}
typ := p.parseType(pkg)
if isVariadic {
typ = types.NewSlice(typ)
}
param = types.NewParam(token.NoPos, pkg, name, typ)
return
}
// parseVar parses a Var:
//
// Var = Name Type .
func (p *parser) parseVar(pkg *types.Package) *types.Var {
name := p.parseName()
v := types.NewVar(token.NoPos, pkg, name, p.parseType(pkg))
typesinternal.SetVarKind(v, typesinternal.PackageVar)
if name[0] == '.' || name[0] == '<' {
// This is an unexported variable,
// or a variable defined in a different package.
// We only want to record exported variables.
return nil
}
return v
}
// parseConversion parses a Conversion:
//
// Conversion = "convert" "(" Type "," ConstValue ")" .
func (p *parser) parseConversion(pkg *types.Package) (val constant.Value, typ types.Type) {
p.expectKeyword("convert")
p.expect('(')
typ = p.parseType(pkg)
p.expect(',')
val, _ = p.parseConstValue(pkg)
p.expect(')')
return
}
// parseConstValue parses a ConstValue:
//
// ConstValue = string | "false" | "true" | ["-"] (int ["'"] | FloatOrComplex) | Conversion .
// FloatOrComplex = float ["i" | ("+"|"-") float "i"] .
func (p *parser) parseConstValue(pkg *types.Package) (val constant.Value, typ types.Type) {
// v3 changed to $false, $true, $convert, to avoid confusion
// with variable names in inline function bodies.
if p.tok == '$' {
p.next()
if p.tok != scanner.Ident {
p.errorf("expected identifier after '$', got %s (%q)", scanner.TokenString(p.tok), p.lit)
}
}
switch p.tok {
case scanner.String:
str := p.parseString()
val = constant.MakeString(str)
typ = types.Typ[types.UntypedString]
return
case scanner.Ident:
b := false
switch p.lit {
case "false":
case "true":
b = true
case "convert":
return p.parseConversion(pkg)
default:
p.errorf("expected const value, got %s (%q)", scanner.TokenString(p.tok), p.lit)
}
p.next()
val = constant.MakeBool(b)
typ = types.Typ[types.UntypedBool]
return
}
sign := ""
if p.tok == '-' {
p.next()
sign = "-"
}
switch p.tok {
case scanner.Int:
val = constant.MakeFromLiteral(sign+p.lit, token.INT, 0)
if val == nil {
p.error("could not parse integer literal")
}
p.next()
if p.tok == '\'' {
p.next()
typ = types.Typ[types.UntypedRune]
} else {
typ = types.Typ[types.UntypedInt]
}
case scanner.Float:
re := sign + p.lit
p.next()
var im string
switch p.tok {
case '+':
p.next()
im = p.expect(scanner.Float)
case '-':
p.next()
im = "-" + p.expect(scanner.Float)
case scanner.Ident:
// re is in fact the imaginary component. Expect "i" below.
im = re
re = "0"
default:
val = constant.MakeFromLiteral(re, token.FLOAT, 0)
if val == nil {
p.error("could not parse float literal")
}
typ = types.Typ[types.UntypedFloat]
return
}
p.expectKeyword("i")
reval := constant.MakeFromLiteral(re, token.FLOAT, 0)
if reval == nil {
p.error("could not parse real component of complex literal")
}
imval := constant.MakeFromLiteral(im+"i", token.IMAG, 0)
if imval == nil {
p.error("could not parse imag component of complex literal")
}
val = constant.BinaryOp(reval, token.ADD, imval)
typ = types.Typ[types.UntypedComplex]
default:
p.errorf("expected const value, got %s (%q)", scanner.TokenString(p.tok), p.lit)
}
return
}
// parseConst parses a Const:
//
// Const = Name [Type] "=" ConstValue .
func (p *parser) parseConst(pkg *types.Package) *types.Const {
name := p.parseName()
var typ types.Type
if p.tok == '<' {
typ = p.parseType(pkg)
}
p.expect('=')
val, vtyp := p.parseConstValue(pkg)
if typ == nil {
typ = vtyp
}
return types.NewConst(token.NoPos, pkg, name, typ, val)
}
// reserved is a singleton type used to fill type map slots that have
// been reserved (i.e., for which a type number has been parsed) but
// which don't have their actual type yet. When the type map is updated,
// the actual type must replace a reserved entry (or we have an internal
// error). Used for self-verification only - not required for correctness.
var reserved = new(struct{ types.Type })
// reserve reserves the type map entry n for future use.
func (p *parser) reserve(n int) {
// Notes:
// - for pre-V3 export data, the type numbers we see are
// guaranteed to be in increasing order, so we append a
// reserved entry onto the list.
// - for V3+ export data, type numbers can appear in
// any order, however the 'types' section tells us the
// total number of types, hence typeList is pre-allocated.
if len(p.typeData) == 0 {
if n != len(p.typeList) {
p.errorf("invalid type number %d (out of sync)", n)
}
p.typeList = append(p.typeList, reserved)
} else {
if p.typeList[n] != nil {
p.errorf("previously visited type number %d", n)
}
p.typeList[n] = reserved
}
}
// update sets the type map entries for the entries in nlist to t.
// An entry in nlist can be a type number in p.typeList,
// used to resolve named types, or it can be a *types.Pointer,
// used to resolve pointers to named types in case they are referenced
// by embedded fields.
func (p *parser) update(t types.Type, nlist []any) {
if t == reserved {
p.errorf("internal error: update(%v) invoked on reserved", nlist)
}
if t == nil {
p.errorf("internal error: update(%v) invoked on nil", nlist)
}
for _, n := range nlist {
switch n := n.(type) {
case int:
if p.typeList[n] == t {
continue
}
if p.typeList[n] != reserved {
p.errorf("internal error: update(%v): %d not reserved", nlist, n)
}
p.typeList[n] = t
case *types.Pointer:
if *n != (types.Pointer{}) {
elem := n.Elem()
if elem == t {
continue
}
p.errorf("internal error: update: pointer already set to %v, expected %v", elem, t)
}
*n = *types.NewPointer(t)
default:
p.errorf("internal error: %T on nlist", n)
}
}
}
// parseNamedType parses a NamedType:
//
// NamedType = TypeName [ "=" ] Type { Method } .
// TypeName = ExportedName .
// Method = "func" "(" Param ")" Name ParamList ResultList [InlineBody] ";" .
func (p *parser) parseNamedType(nlist []any) types.Type {
pkg, name := p.parseExportedName()
scope := pkg.Scope()
obj := scope.Lookup(name)
if obj != nil && obj.Type() == nil {
p.errorf("%v has nil type", obj)
}
if p.tok == scanner.Ident && p.lit == "notinheap" {
p.next()
// The go/types package has no way of recording that
// this type is marked notinheap. Presumably no user
// of this package actually cares.
}
// type alias
if p.tok == '=' {
p.next()
p.aliases[nlist[len(nlist)-1].(int)] = name
if obj != nil {
// use the previously imported (canonical) type
t := obj.Type()
p.update(t, nlist)
p.parseType(pkg) // discard
return t
}
t := p.parseType(pkg, nlist...)
obj = types.NewTypeName(token.NoPos, pkg, name, t)
scope.Insert(obj)
return t
}
// defined type
if obj == nil {
// A named type may be referred to before the underlying type
// is known - set it up.
tname := types.NewTypeName(token.NoPos, pkg, name, nil)
types.NewNamed(tname, nil, nil)
scope.Insert(tname)
obj = tname
}
// use the previously imported (canonical), or newly created type
t := obj.Type()
p.update(t, nlist)
nt, ok := types.Unalias(t).(*types.Named)
if !ok {
// This can happen for unsafe.Pointer, which is a TypeName holding a Basic type.
pt := p.parseType(pkg)
if pt != t {
p.error("unexpected underlying type for non-named TypeName")
}
return t
}
underlying := p.parseType(pkg)
if nt.Underlying() == nil {
if underlying.Underlying() == nil {
fix := fixupRecord{toUpdate: nt, target: underlying}
p.fixups = append(p.fixups, fix)
} else {
nt.SetUnderlying(underlying.Underlying())
}
}
if p.tok == '\n' {
p.next()
// collect associated methods
for p.tok == scanner.Ident {
p.expectKeyword("func")
if p.tok == '/' {
// Skip a /*nointerface*/ or /*asm ID */ comment.
p.expect('/')
p.expect('*')
if p.expect(scanner.Ident) == "asm" {
p.parseUnquotedString()
}
p.expect('*')
p.expect('/')
}
p.expect('(')
receiver, _ := p.parseParam(pkg)
p.expect(')')
name := p.parseName()
params, isVariadic := p.parseParamList(pkg)
results := p.parseResultList(pkg)
p.skipInlineBody()
p.expectEOL()
sig := types.NewSignatureType(receiver, nil, nil, params, results, isVariadic)
nt.AddMethod(types.NewFunc(token.NoPos, pkg, name, sig))
}
}
return nt
}
func (p *parser) parseInt64() int64 {
lit := p.expect(scanner.Int)
n, err := strconv.ParseInt(lit, 10, 64)
if err != nil {
p.error(err)
}
return n
}
func (p *parser) parseInt() int {
lit := p.expect(scanner.Int)
n, err := strconv.ParseInt(lit, 10, 0 /* int */)
if err != nil {
p.error(err)
}
return int(n)
}
// parseArrayOrSliceType parses an ArrayOrSliceType:
//
// ArrayOrSliceType = "[" [ int ] "]" Type .
func (p *parser) parseArrayOrSliceType(pkg *types.Package, nlist []any) types.Type {
p.expect('[')
if p.tok == ']' {
p.next()
t := new(types.Slice)
p.update(t, nlist)
*t = *types.NewSlice(p.parseType(pkg))
return t
}
t := new(types.Array)
p.update(t, nlist)
len := p.parseInt64()
p.expect(']')
*t = *types.NewArray(p.parseType(pkg), len)
return t
}
// parseMapType parses a MapType:
//
// MapType = "map" "[" Type "]" Type .
func (p *parser) parseMapType(pkg *types.Package, nlist []any) types.Type {
p.expectKeyword("map")
t := new(types.Map)
p.update(t, nlist)
p.expect('[')
key := p.parseType(pkg)
p.expect(']')
elem := p.parseType(pkg)
*t = *types.NewMap(key, elem)
return t
}
// parseChanType parses a ChanType:
//
// ChanType = "chan" ["<-" | "-<"] Type .
func (p *parser) parseChanType(pkg *types.Package, nlist []any) types.Type {
p.expectKeyword("chan")
t := new(types.Chan)
p.update(t, nlist)
dir := types.SendRecv
switch p.tok {
case '-':
p.next()
p.expect('<')
dir = types.SendOnly
case '<':
// don't consume '<' if it belongs to Type
if p.scanner.Peek() == '-' {
p.next()
p.expect('-')
dir = types.RecvOnly
}
}
*t = *types.NewChan(dir, p.parseType(pkg))
return t
}
// parseStructType parses a StructType:
//
// StructType = "struct" "{" { Field } "}" .
func (p *parser) parseStructType(pkg *types.Package, nlist []any) types.Type {
p.expectKeyword("struct")
t := new(types.Struct)
p.update(t, nlist)
var fields []*types.Var
var tags []string
p.expect('{')
for p.tok != '}' && p.tok != scanner.EOF {
field, tag := p.parseField(pkg)
p.expect(';')
fields = append(fields, field)
tags = append(tags, tag)
}
p.expect('}')
*t = *types.NewStruct(fields, tags)
return t
}
// parseParamList parses a ParamList:
//
// ParamList = "(" [ { Parameter "," } Parameter ] ")" .
func (p *parser) parseParamList(pkg *types.Package) (*types.Tuple, bool) {
var list []*types.Var
isVariadic := false
p.expect('(')
for p.tok != ')' && p.tok != scanner.EOF {
if len(list) > 0 {
p.expect(',')
}
par, variadic := p.parseParam(pkg)
list = append(list, par)
if variadic {
if isVariadic {
p.error("... not on final argument")
}
isVariadic = true
}
}
p.expect(')')
return types.NewTuple(list...), isVariadic
}
// parseResultList parses a ResultList:
//
// ResultList = Type | ParamList .
func (p *parser) parseResultList(pkg *types.Package) *types.Tuple {
switch p.tok {
case '<':
p.next()
if p.tok == scanner.Ident && p.lit == "inl" {
return nil
}
taa, _ := p.parseTypeAfterAngle(pkg)
return types.NewTuple(types.NewParam(token.NoPos, pkg, "", taa))
case '(':
params, _ := p.parseParamList(pkg)
return params
default:
return nil
}
}
// parseFunctionType parses a FunctionType:
//
// FunctionType = ParamList ResultList .
func (p *parser) parseFunctionType(pkg *types.Package, nlist []any) *types.Signature {
t := new(types.Signature)
p.update(t, nlist)
params, isVariadic := p.parseParamList(pkg)
results := p.parseResultList(pkg)
*t = *types.NewSignatureType(nil, nil, nil, params, results, isVariadic)
return t
}
// parseFunc parses a Func:
//
// Func = Name FunctionType [InlineBody] .
func (p *parser) parseFunc(pkg *types.Package) *types.Func {
if p.tok == '/' {
// Skip an /*asm ID */ comment.
p.expect('/')
p.expect('*')
if p.expect(scanner.Ident) == "asm" {
p.parseUnquotedString()
}
p.expect('*')
p.expect('/')
}
name := p.parseName()
f := types.NewFunc(token.NoPos, pkg, name, p.parseFunctionType(pkg, nil))
p.skipInlineBody()
if name[0] == '.' || name[0] == '<' || strings.ContainsRune(name, '$') {
// This is an unexported function,
// or a function defined in a different package,
// or a type$equal or type$hash function.
// We only want to record exported functions.
return nil
}
return f
}
// parseInterfaceType parses an InterfaceType:
//
// InterfaceType = "interface" "{" { ("?" Type | Func) ";" } "}" .
func (p *parser) parseInterfaceType(pkg *types.Package, nlist []any) types.Type {
p.expectKeyword("interface")
t := new(types.Interface)
p.update(t, nlist)
var methods []*types.Func
var embeddeds []types.Type
p.expect('{')
for p.tok != '}' && p.tok != scanner.EOF {
if p.tok == '?' {
p.next()
embeddeds = append(embeddeds, p.parseType(pkg))
} else {
method := p.parseFunc(pkg)
if method != nil {
methods = append(methods, method)
}
}
p.expect(';')
}
p.expect('}')
*t = *newInterface(methods, embeddeds)
return t
}
// parsePointerType parses a PointerType:
//
// PointerType = "*" ("any" | Type) .
func (p *parser) parsePointerType(pkg *types.Package, nlist []any) types.Type {
p.expect('*')
if p.tok == scanner.Ident {
p.expectKeyword("any")
t := types.Typ[types.UnsafePointer]
p.update(t, nlist)
return t
}
t := new(types.Pointer)
p.update(t, nlist)
*t = *types.NewPointer(p.parseType(pkg, t))
return t
}
// parseTypeSpec parses a TypeSpec:
//
// TypeSpec = NamedType | MapType | ChanType | StructType | InterfaceType | PointerType | ArrayOrSliceType | FunctionType .
func (p *parser) parseTypeSpec(pkg *types.Package, nlist []any) types.Type {
switch p.tok {
case scanner.String:
return p.parseNamedType(nlist)
case scanner.Ident:
switch p.lit {
case "map":
return p.parseMapType(pkg, nlist)
case "chan":
return p.parseChanType(pkg, nlist)
case "struct":
return p.parseStructType(pkg, nlist)
case "interface":
return p.parseInterfaceType(pkg, nlist)
}
case '*':
return p.parsePointerType(pkg, nlist)
case '[':
return p.parseArrayOrSliceType(pkg, nlist)
case '(':
return p.parseFunctionType(pkg, nlist)
}
p.errorf("expected type name or literal, got %s", scanner.TokenString(p.tok))
return nil
}
const (
// From gofrontend/go/export.h
// Note that these values are negative in the gofrontend and have been made positive
// in the gccgoimporter.
gccgoBuiltinINT8 = 1
gccgoBuiltinINT16 = 2
gccgoBuiltinINT32 = 3
gccgoBuiltinINT64 = 4
gccgoBuiltinUINT8 = 5
gccgoBuiltinUINT16 = 6
gccgoBuiltinUINT32 = 7
gccgoBuiltinUINT64 = 8
gccgoBuiltinFLOAT32 = 9
gccgoBuiltinFLOAT64 = 10
gccgoBuiltinINT = 11
gccgoBuiltinUINT = 12
gccgoBuiltinUINTPTR = 13
gccgoBuiltinBOOL = 15
gccgoBuiltinSTRING = 16
gccgoBuiltinCOMPLEX64 = 17
gccgoBuiltinCOMPLEX128 = 18
gccgoBuiltinERROR = 19
gccgoBuiltinBYTE = 20
gccgoBuiltinRUNE = 21
gccgoBuiltinANY = 22
)
func lookupBuiltinType(typ int) types.Type {
return [...]types.Type{
gccgoBuiltinINT8: types.Typ[types.Int8],
gccgoBuiltinINT16: types.Typ[types.Int16],
gccgoBuiltinINT32: types.Typ[types.Int32],
gccgoBuiltinINT64: types.Typ[types.Int64],
gccgoBuiltinUINT8: types.Typ[types.Uint8],
gccgoBuiltinUINT16: types.Typ[types.Uint16],
gccgoBuiltinUINT32: types.Typ[types.Uint32],
gccgoBuiltinUINT64: types.Typ[types.Uint64],
gccgoBuiltinFLOAT32: types.Typ[types.Float32],
gccgoBuiltinFLOAT64: types.Typ[types.Float64],
gccgoBuiltinINT: types.Typ[types.Int],
gccgoBuiltinUINT: types.Typ[types.Uint],
gccgoBuiltinUINTPTR: types.Typ[types.Uintptr],
gccgoBuiltinBOOL: types.Typ[types.Bool],
gccgoBuiltinSTRING: types.Typ[types.String],
gccgoBuiltinCOMPLEX64: types.Typ[types.Complex64],
gccgoBuiltinCOMPLEX128: types.Typ[types.Complex128],
gccgoBuiltinERROR: types.Universe.Lookup("error").Type(),
gccgoBuiltinBYTE: types.Universe.Lookup("byte").Type(),
gccgoBuiltinRUNE: types.Universe.Lookup("rune").Type(),
gccgoBuiltinANY: types.Universe.Lookup("any").Type(),
}[typ]
}
// parseType parses a Type:
//
// Type = "<" "type" ( "-" int | int [ TypeSpec ] ) ">" .
//
// parseType updates the type map to t for all type numbers n.
func (p *parser) parseType(pkg *types.Package, n ...any) types.Type {
p.expect('<')
t, _ := p.parseTypeAfterAngle(pkg, n...)
return t
}
// (*parser).Type after reading the "<".
func (p *parser) parseTypeAfterAngle(pkg *types.Package, n ...any) (t types.Type, n1 int) {
p.expectKeyword("type")
n1 = 0
switch p.tok {
case scanner.Int:
n1 = p.parseInt()
if p.tok == '>' {
if len(p.typeData) > 0 && p.typeList[n1] == nil {
p.parseSavedType(pkg, n1, n)
}
t = p.typeList[n1]
if len(p.typeData) == 0 && t == reserved {
p.errorf("invalid type cycle, type %d not yet defined (nlist=%v)", n1, n)
}
p.update(t, n)
} else {
p.reserve(n1)
t = p.parseTypeSpec(pkg, append(n, n1))
}
case '-':
p.next()
n1 := p.parseInt()
t = lookupBuiltinType(n1)
p.update(t, n)
default:
p.errorf("expected type number, got %s (%q)", scanner.TokenString(p.tok), p.lit)
return nil, 0
}
if t == nil || t == reserved {
p.errorf("internal error: bad return from parseType(%v)", n)
}
p.expect('>')
return
}
// parseTypeExtended is identical to parseType, but if the type in
// question is a saved type, returns the index as well as the type
// pointer (index returned is zero if we parsed a builtin).
func (p *parser) parseTypeExtended(pkg *types.Package, n ...any) (t types.Type, n1 int) {
p.expect('<')
t, n1 = p.parseTypeAfterAngle(pkg, n...)
return
}
// InlineBody = "" .{NN}
// Reports whether a body was skipped.
func (p *parser) skipInlineBody() {
// We may or may not have seen the '<' already, depending on
// whether the function had a result type or not.
if p.tok == '<' {
p.next()
p.expectKeyword("inl")
} else if p.tok != scanner.Ident || p.lit != "inl" {
return
} else {
p.next()
}
p.expect(':')
want := p.parseInt()
p.expect('>')
defer func(w uint64) {
p.scanner.Whitespace = w
}(p.scanner.Whitespace)
p.scanner.Whitespace = 0
got := 0
for got < want {
r := p.scanner.Next()
if r == scanner.EOF {
p.error("unexpected EOF")
}
got += utf8.RuneLen(r)
}
}
// parseTypes parses a Types:
//
// Types = "types" maxp1 exportedp1 (offset length)* .
func (p *parser) parseTypes(pkg *types.Package) {
maxp1 := p.parseInt()
exportedp1 := p.parseInt()
p.typeList = make([]types.Type, maxp1)
type typeOffset struct {
offset int
length int
}
var typeOffsets []typeOffset
total := 0
for i := 1; i < maxp1; i++ {
len := p.parseInt()
typeOffsets = append(typeOffsets, typeOffset{total, len})
total += len
}
defer func(w uint64) {
p.scanner.Whitespace = w
}(p.scanner.Whitespace)
p.scanner.Whitespace = 0
// We should now have p.tok pointing to the final newline.
// The next runes from the scanner should be the type data.
var sb strings.Builder
for sb.Len() < total {
r := p.scanner.Next()
if r == scanner.EOF {
p.error("unexpected EOF")
}
sb.WriteRune(r)
}
allTypeData := sb.String()
p.typeData = []string{""} // type 0, unused
for _, to := range typeOffsets {
p.typeData = append(p.typeData, allTypeData[to.offset:to.offset+to.length])
}
for i := 1; i < int(exportedp1); i++ {
p.parseSavedType(pkg, i, nil)
}
}
// parseSavedType parses one saved type definition.
func (p *parser) parseSavedType(pkg *types.Package, i int, nlist []any) {
defer func(s *scanner.Scanner, tok rune, lit string) {
p.scanner = s
p.tok = tok
p.lit = lit
}(p.scanner, p.tok, p.lit)
p.scanner = new(scanner.Scanner)
p.initScanner(p.scanner.Filename, strings.NewReader(p.typeData[i]))
p.expectKeyword("type")
id := p.parseInt()
if id != i {
p.errorf("type ID mismatch: got %d, want %d", id, i)
}
if p.typeList[i] == reserved {
p.errorf("internal error: %d already reserved in parseSavedType", i)
}
if p.typeList[i] == nil {
p.reserve(i)
p.parseTypeSpec(pkg, append(nlist, i))
}
if p.typeList[i] == nil || p.typeList[i] == reserved {
p.errorf("internal error: parseSavedType(%d,%v) reserved/nil", i, nlist)
}
}
// parsePackageInit parses a PackageInit:
//
// PackageInit = unquotedString unquotedString int .
func (p *parser) parsePackageInit() PackageInit {
name := p.parseUnquotedString()
initfunc := p.parseUnquotedString()
priority := -1
if p.version == "v1" {
priority = p.parseInt()
}
return PackageInit{Name: name, InitFunc: initfunc, Priority: priority}
}
// Create the package if we have parsed both the package path and package name.
func (p *parser) maybeCreatePackage() {
if p.pkgname != "" && p.pkgpath != "" {
p.pkg = p.getPkg(p.pkgpath, p.pkgname)
}
}
// parseInitDataDirective parses an InitDataDirective:
//
// InitDataDirective = ( "v1" | "v2" | "v3" ) ";" |
// "priority" int ";" |
// "init" { PackageInit } ";" |
// "checksum" unquotedString ";" .
func (p *parser) parseInitDataDirective() {
if p.tok != scanner.Ident {
// unexpected token kind; panic
p.expect(scanner.Ident)
}
switch p.lit {
case "v1", "v2", "v3":
p.version = p.lit
p.next()
p.expect(';')
p.expect('\n')
case "priority":
p.next()
p.initdata.Priority = p.parseInt()
p.expectEOL()
case "init":
p.next()
for p.tok != '\n' && p.tok != ';' && p.tok != scanner.EOF {
p.initdata.Inits = append(p.initdata.Inits, p.parsePackageInit())
}
p.expectEOL()
case "init_graph":
p.next()
// The graph data is thrown away for now.
for p.tok != '\n' && p.tok != ';' && p.tok != scanner.EOF {
p.parseInt64()
p.parseInt64()
}
p.expectEOL()
case "checksum":
// Don't let the scanner try to parse the checksum as a number.
defer func(mode uint) {
p.scanner.Mode = mode
}(p.scanner.Mode)
p.scanner.Mode &^= scanner.ScanInts | scanner.ScanFloats
p.next()
p.parseUnquotedString()
p.expectEOL()
default:
p.errorf("unexpected identifier: %q", p.lit)
}
}
// parseDirective parses a Directive:
//
// Directive = InitDataDirective |
// "package" unquotedString [ unquotedString ] [ unquotedString ] ";" |
// "pkgpath" unquotedString ";" |
// "prefix" unquotedString ";" |
// "import" unquotedString unquotedString string ";" |
// "indirectimport" unquotedString unquotedstring ";" |
// "func" Func ";" |
// "type" Type ";" |
// "var" Var ";" |
// "const" Const ";" .
func (p *parser) parseDirective() {
if p.tok != scanner.Ident {
// unexpected token kind; panic
p.expect(scanner.Ident)
}
switch p.lit {
case "v1", "v2", "v3", "priority", "init", "init_graph", "checksum":
p.parseInitDataDirective()
case "package":
p.next()
p.pkgname = p.parseUnquotedString()
p.maybeCreatePackage()
if p.version != "v1" && p.tok != '\n' && p.tok != ';' {
p.parseUnquotedString()
p.parseUnquotedString()
}
p.expectEOL()
case "pkgpath":
p.next()
p.pkgpath = p.parseUnquotedString()
p.maybeCreatePackage()
p.expectEOL()
case "prefix":
p.next()
p.pkgpath = p.parseUnquotedString()
p.expectEOL()
case "import":
p.next()
pkgname := p.parseUnquotedString()
pkgpath := p.parseUnquotedString()
p.getPkg(pkgpath, pkgname)
p.parseString()
p.expectEOL()
case "indirectimport":
p.next()
pkgname := p.parseUnquotedString()
pkgpath := p.parseUnquotedString()
p.getPkg(pkgpath, pkgname)
p.expectEOL()
case "types":
p.next()
p.parseTypes(p.pkg)
p.expectEOL()
case "func":
p.next()
fun := p.parseFunc(p.pkg)
if fun != nil {
p.pkg.Scope().Insert(fun)
}
p.expectEOL()
case "type":
p.next()
p.parseType(p.pkg)
p.expectEOL()
case "var":
p.next()
v := p.parseVar(p.pkg)
if v != nil {
p.pkg.Scope().Insert(v)
}
p.expectEOL()
case "const":
p.next()
c := p.parseConst(p.pkg)
p.pkg.Scope().Insert(c)
p.expectEOL()
default:
p.errorf("unexpected identifier: %q", p.lit)
}
}
// parsePackage parses a Package:
//
// Package = { Directive } .
func (p *parser) parsePackage() *types.Package {
for p.tok != scanner.EOF {
p.parseDirective()
}
for _, f := range p.fixups {
if f.target.Underlying() == nil {
p.errorf("internal error: fixup can't be applied, loop required")
}
f.toUpdate.SetUnderlying(f.target.Underlying())
}
p.fixups = nil
for _, typ := range p.typeList {
if it, ok := types.Unalias(typ).(*types.Interface); ok {
it.Complete()
}
}
p.pkg.MarkComplete()
return p.pkg
}
================================================
FILE: go/internal/gccgoimporter/parser_test.go
================================================
// Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Except for this comment, this file is a verbatim copy of the file
// with the same name in $GOROOT/src/go/internal/gccgoimporter.
package gccgoimporter
import (
"bytes"
"go/types"
"strings"
"testing"
"text/scanner"
)
var typeParserTests = []struct {
id, typ, want, underlying, methods string
}{
{id: "foo", typ: "", want: "int8"},
{id: "foo", typ: ">", want: "*error"},
{id: "foo", typ: "", want: "unsafe.Pointer"},
{id: "foo", typ: ">>", want: "foo.Bar", underlying: "*foo.Bar"},
{id: "foo", typ: "\nfunc (? ) M ();\n>", want: "bar.Foo", underlying: "int8", methods: "func (bar.Foo).M()"},
{id: "foo", typ: ">", want: "bar.foo", underlying: "int8"},
{id: "foo", typ: ">", want: "[]int8"},
{id: "foo", typ: ">", want: "[42]int8"},
{id: "foo", typ: "] >", want: "map[int8]int16"},
{id: "foo", typ: ">", want: "chan int8"},
{id: "foo", typ: ">", want: "<-chan int8"},
{id: "foo", typ: ">", want: "chan<- int8"},
{id: "foo", typ: "; I16 \"i16\"; }>", want: "struct{I8 int8; I16 int16 \"i16\"}"},
{id: "foo", typ: ", b ) ; Bar (? , ? ...) (? , ? ); Baz (); }>", want: "interface{Bar(int16, ...int8) (int16, int8); Baz(); Foo(a int8, b int16) int8}"},
{id: "foo", typ: ") >", want: "func(int8) int16"},
}
func TestTypeParser(t *testing.T) {
for _, test := range typeParserTests {
var p parser
p.init("test.gox", strings.NewReader(test.typ), make(map[string]*types.Package))
p.version = "v2"
p.pkgname = test.id
p.pkgpath = test.id
p.maybeCreatePackage()
typ := p.parseType(p.pkg)
if p.tok != scanner.EOF {
t.Errorf("expected full parse, stopped at %q", p.lit)
}
// interfaces must be explicitly completed
if ityp, _ := typ.(*types.Interface); ityp != nil {
ityp.Complete()
}
got := typ.String()
if got != test.want {
t.Errorf("got type %q, expected %q", got, test.want)
}
if test.underlying != "" {
underlying := typ.Underlying().String()
if underlying != test.underlying {
t.Errorf("got underlying type %q, expected %q", underlying, test.underlying)
}
}
if test.methods != "" {
nt := typ.(*types.Named)
var buf bytes.Buffer
for i := 0; i != nt.NumMethods(); i++ {
buf.WriteString(nt.Method(i).String())
}
methods := buf.String()
if methods != test.methods {
t.Errorf("got methods %q, expected %q", methods, test.methods)
}
}
}
}
================================================
FILE: go/internal/gccgoimporter/testdata/aliases.go
================================================
package aliases
type (
T0 [10]int
T1 []byte
T2 struct {
x int
}
T3 interface {
m() T2
}
T4 func(int, T0) chan T2
)
// basic aliases
type (
Ai = int
A0 = T0
A1 = T1
A2 = T2
A3 = T3
A4 = T4
A10 = [10]int
A11 = []byte
A12 = struct {
x int
}
A13 = interface {
m() A2
}
A14 = func(int, A0) chan A2
)
// alias receiver types
func (T0) m1() {}
func (A0) m2() {}
// alias receiver types (long type declaration chains)
type (
V0 = V1
V1 = (V2)
V2 = (V3)
V3 = T0
)
func (V1) n() {}
// cycles
type C0 struct {
f1 C1
f2 C2
}
type (
C1 *C0
C2 = C1
)
type (
C5 struct {
f *C6
}
C6 = C5
)
================================================
FILE: go/internal/gccgoimporter/testdata/aliases.gox
================================================
v2;
package aliases;
prefix go;
package aliases go.aliases go.aliases;
type >
func (? ) .go.aliases.m1 ();
func (? ) .go.aliases.m2 ();
func (? >>>) .go.aliases.n ();
>>;
type >>>;
type >>;
type >>;
type ; }>>;
type ; }>>>; }>>;
type , ? ) >>>;
type ;
type ; }>>>;
type , ? ) >>>>;
type >;
type >>; .go.aliases.f2 >; }>>;
type ;
type ;
type >>; }>>;
type ;
type ;
type ;
type ;
type ;
type ;
type >;
type ;
type ;
type ;
================================================
FILE: go/internal/gccgoimporter/testdata/complexnums.go
================================================
package complexnums
const NN = -1 - 1i
const NP = -1 + 1i
const PN = 1 - 1i
const PP = 1 + 1i
================================================
FILE: go/internal/gccgoimporter/testdata/complexnums.gox
================================================
v1;
package complexnums;
pkgpath complexnums;
priority 1;
const NN = -0.1E1-0.1E1i ;
const NP = -0.1E1+0.1E1i ;
const PN = 0.1E1-0.1E1i ;
const PP = 0.1E1+0.1E1i ;
================================================
FILE: go/internal/gccgoimporter/testdata/conversions.go
================================================
package conversions
type Units string
const Bits = Units("bits")
================================================
FILE: go/internal/gccgoimporter/testdata/conversions.gox
================================================
v2;
package conversions;
prefix go;
package conversions go.conversions go.conversions;
const Bits > = convert(, "bits");
type ;
================================================
FILE: go/internal/gccgoimporter/testdata/escapeinfo.go
================================================
// Test case for escape info in export data. To compile and extract .gox file:
// gccgo -fgo-optimize-allocs -c escapeinfo.go
// objcopy -j .go_export escapeinfo.o escapeinfo.gox
package escapeinfo
type T struct{ data []byte }
func NewT(data []byte) *T {
return &T{data}
}
func (*T) Read(p []byte) {}
================================================
FILE: go/internal/gccgoimporter/testdata/escapeinfo.gox
================================================
v2;
package escapeinfo;
prefix go;
package escapeinfo go.escapeinfo go.escapeinfo;
func NewT (data >) >; }>
func (? >) Read (p >);
>>;
type ;
checksum 3500838130783C0059CD0C81527F60E9738E3ACE;
================================================
FILE: go/internal/gccgoimporter/testdata/imports.go
================================================
package imports
import "fmt"
var Hello = fmt.Sprintf("Hello, world")
================================================
FILE: go/internal/gccgoimporter/testdata/imports.gox
================================================
v1;
package imports;
pkgpath imports;
priority 7;
import fmt fmt "fmt";
init imports imports..import 7 math math..import 1 runtime runtime..import 1 strconv strconv..import 2 io io..import 3 reflect reflect..import 3 syscall syscall..import 3 time time..import 4 os os..import 5 fmt fmt..import 6;
var Hello ;
================================================
FILE: go/internal/gccgoimporter/testdata/issue27856.go
================================================
package lib
type M struct {
E E
}
type F struct {
_ *M
}
type E = F
================================================
FILE: go/internal/gccgoimporter/testdata/issue27856.gox
================================================
v2;
package main;
pkgpath main;
import runtime runtime "runtime";
init runtime runtime..import sys runtime_internal_sys..import;
init_graph 0 1;
type ; }>>>; }>>>;
type ;
type ;
================================================
FILE: go/internal/gccgoimporter/testdata/issue29198.go
================================================
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package server
import (
"context"
"errors"
)
type A struct {
x int
}
func (a *A) AMethod(y int) *Server {
return nil
}
// FooServer is a server that provides Foo services
type FooServer Server
func (f *FooServer) WriteEvents(ctx context.Context, x int) error {
return errors.New("hey!")
}
type Server struct {
FooServer *FooServer
user string
ctx context.Context
}
func New(sctx context.Context, u string) (*Server, error) {
s := &Server{user: u, ctx: sctx}
s.FooServer = (*FooServer)(s)
return s, nil
}
================================================
FILE: go/internal/gccgoimporter/testdata/issue29198.gox
================================================
v2;
package server;
pkgpath issue29198;
import context context "context";
import errors errors "errors";
init context context..import fmt fmt..import poll internal_poll..import testlog internal_testlog..import io io..import os os..import reflect reflect..import runtime runtime..import sys runtime_internal_sys..import strconv strconv..import sync sync..import syscall syscall..import time time..import unicode unicode..import;
init_graph 0 1 0 2 0 3 0 4 0 5 0 6 0 7 0 8 0 9 0 10 0 11 0 12 0 13 1 2 1 3 1 4 1 5 1 6 1 7 1 8 1 9 1 10 1 11 1 12 1 13 2 4 2 7 2 8 2 10 2 11 2 12 4 7 4 8 4 10 5 2 5 3 5 4 5 7 5 8 5 10 5 11 5 12 6 7 6 8 6 9 6 10 6 13 7 8 9 7 9 8 10 7 10 8 11 7 11 8 11 10 12 7 12 8 12 10 12 11;
type ; }>
func (a >) AMethod (y )
func (f >) WriteEvents (ctx ; .time.ext ; .time.loc ; .time.zone ; .time.offset ; .time.isDST ; }>>>; .time.tx ; .time.index ; .time.isstd ; .time.isutc ; }>>>; .time.cacheStart ; .time.cacheEnd ; .time.cacheZone >; }>
func (l >) String () ;
func (l ) .time.lookupFirstZone () ;
func (l ) .time.get () ;
func (l