* Go gRPC stub → impl bridge. The protoc-gen-go-grpc codegen emits an * `UnimplementedXxxServer` struct in `*_grpc.pb.go` carrying one method * per service RPC; the real handler is a hand-written struct in another * file (`x/bank/keeper/msg_server.go::msgServer.Send` in cosmos-sdk). * Go's struc
(queries: QueryBuilder, onYield: MaybeYield)
| 1136 | * stub's source line is the wiring site shown in the trace trail. |
| 1137 | */ |
| 1138 | async function goGrpcStubImplEdges(queries: QueryBuilder, onYield: MaybeYield): Promise<Edge[]> { |
| 1139 | let scanned255 = 0; |
| 1140 | const edges: Edge[] = []; |
| 1141 | const seen = new Set<string>(); |
| 1142 | |
| 1143 | const STUB_RE = /^Unimplemented.*Server$/; |
| 1144 | // gRPC internal-helper methods that appear on every Unimplemented*Server; |
| 1145 | // not part of the service contract, so exclude when computing the RPC-method |
| 1146 | // signature used to match impls. |
| 1147 | const isInternalMarker = (n: string) => n.startsWith('mustEmbed') || n === 'testEmbeddedByValue'; |
| 1148 | |
| 1149 | // Methods directly contained by each Go struct, name-only. Built once. |
| 1150 | const methodNamesByStruct = new Map<string, Set<string>>(); |
| 1151 | const methodNodesByStruct = new Map<string, Node[]>(); |
| 1152 | const goStructs: Node[] = []; |
| 1153 | for (const s of queries.iterateNodesByKind('struct')) { |
| 1154 | if ((++scanned255 & 63) === 0) await onYield(); |
| 1155 | if (s.language !== 'go') continue; |
| 1156 | goStructs.push(s); |
| 1157 | const ms = queries |
| 1158 | .getOutgoingEdges(s.id, ['contains']) |
| 1159 | .map((e) => queries.getNodeById(e.target)) |
| 1160 | .filter((n): n is Node => !!n && n.kind === 'method'); |
| 1161 | methodNodesByStruct.set(s.id, ms); |
| 1162 | methodNamesByStruct.set(s.id, new Set(ms.map((m) => m.name))); |
| 1163 | } |
| 1164 | |
| 1165 | for (const stub of goStructs) { |
| 1166 | if (!STUB_RE.test(stub.name)) continue; |
| 1167 | // The stub MUST live in a generated file — that's what tells us this is |
| 1168 | // a protoc-emitted scaffold rather than someone naming a struct |
| 1169 | // `UnimplementedXxxServer` by hand. Without this gate we'd also bridge |
| 1170 | // such hand-written structs and create misleading edges. |
| 1171 | if (!isGeneratedFile(stub.filePath)) continue; |
| 1172 | |
| 1173 | const stubMethods = (methodNodesByStruct.get(stub.id) ?? []).filter( |
| 1174 | (m) => !isInternalMarker(m.name), |
| 1175 | ); |
| 1176 | if (stubMethods.length === 0) continue; |
| 1177 | const stubMethodNames = stubMethods.map((m) => m.name); |
| 1178 | |
| 1179 | for (const cand of goStructs) { |
| 1180 | if (cand.id === stub.id) continue; |
| 1181 | // Skip generated-file candidates — they're siblings (msgClient, |
| 1182 | // UnsafeMsgServer, …) whose method sets coincidentally match. |
| 1183 | if (isGeneratedFile(cand.filePath)) continue; |
| 1184 | |
| 1185 | const candNames = methodNamesByStruct.get(cand.id); |
| 1186 | if (!candNames) continue; |
| 1187 | // Subset: every RPC method must exist on the candidate by name. |
| 1188 | // Signature-level match would tighten this further, but name-match |
| 1189 | // alone already gives one-to-one pairing in real codebases because |
| 1190 | // gRPC method-name sets are highly distinctive (Send + MultiSend + |
| 1191 | // UpdateParams + SetSendEnabled is unique to bank's MsgServer). |
| 1192 | if (!stubMethodNames.every((n) => candNames.has(n))) continue; |
| 1193 | |
| 1194 | const candMethods = methodNodesByStruct.get(cand.id) ?? []; |
| 1195 | let added = 0; |
no test coverage detected