Description
When a Ballerina listener's attach() method accepts a union service type, the compiler silently skips injecting the addServiceListener call into the generated call dispatch method. As a result, those services never appear in the runtime repository (env.getRepository().getArtifacts()), causing any tool that depends on runtime artifact discovery (e.g. WSO2 ICP) to report empty listeners and services for non-HTTP event-driven integrations.
Affected versions
Confirmed on 2201.13.3. Likely affects all versions that include the addServiceListener injection in JvmObjectGen.
Root cause
JvmObjectGen.isListenerAttach() determines whether to inject addServiceListener after a listener's attach() call:
// Before fix
private boolean isListenerAttach(BIRNode.BIRFunction func) {
return func.name.value.equals("attach") &&
Symbols.isFlagOn(func.parameters.getFirst().type.getFlags(), Flags.SERVICE);
}
Two problems:
-
Union service types are not detected. BUnionType does not carry Flags.SERVICE on its own flags, even when every member of the union is a service type. For example, the Salesforce connector defines:
public type Service CdcService|PlatformEventsService;
public function attach(Service s, string|string[]? name = ()) returns error? { ... }
The Service parameter type is a BUnionType — isFlagOn(..., Flags.SERVICE) returns false, so injection is skipped.
-
No arity guard. func.parameters.getFirst() throws NoSuchElementException if the function has no parameters, and the injection site unconditionally reads args[1] (the attach-point argument) without verifying the args array has at least two elements.
Impact
Any listener whose attach() parameter is a union of service types will have its services silently dropped from the runtime repository. Heartbeats, monitoring dashboards, and service-discovery tools that read env.getRepository().getArtifacts() will show no listeners or services for those integrations.
Fix
Introduce isServiceType(BType) that recurses into union members and unwraps BTypeReferenceType before checking Flags.SERVICE. Add a size() >= 2 arity guard to protect both getFirst() and the runtime args[1] access:
private boolean isListenerAttach(BIRNode.BIRFunction func) {
return func.name.value.equals("attach") &&
func.parameters.size() >= 2 &&
isServiceType(func.parameters.getFirst().type);
}
private boolean isServiceType(BType type) {
if (Symbols.isFlagOn(type.getFlags(), Flags.SERVICE)) {
return true;
}
if (type instanceof BUnionType unionType) {
return unionType.getMemberTypes().stream().allMatch(this::isServiceType);
}
if (type instanceof BTypeReferenceType refType) {
return refType.referredType != null && isServiceType(refType.referredType);
}
return false;
}
PRs
Description
When a Ballerina listener's
attach()method accepts a union service type, the compiler silently skips injecting theaddServiceListenercall into the generatedcalldispatch method. As a result, those services never appear in the runtime repository (env.getRepository().getArtifacts()), causing any tool that depends on runtime artifact discovery (e.g. WSO2 ICP) to report empty listeners and services for non-HTTP event-driven integrations.Affected versions
Confirmed on
2201.13.3. Likely affects all versions that include theaddServiceListenerinjection inJvmObjectGen.Root cause
JvmObjectGen.isListenerAttach()determines whether to injectaddServiceListenerafter a listener'sattach()call:Two problems:
Union service types are not detected.
BUnionTypedoes not carryFlags.SERVICEon its own flags, even when every member of the union is a service type. For example, the Salesforce connector defines:The
Serviceparameter type is aBUnionType—isFlagOn(..., Flags.SERVICE)returnsfalse, so injection is skipped.No arity guard.
func.parameters.getFirst()throwsNoSuchElementExceptionif the function has no parameters, and the injection site unconditionally readsargs[1](the attach-point argument) without verifying the args array has at least two elements.Impact
Any listener whose
attach()parameter is a union of service types will have its services silently dropped from the runtime repository. Heartbeats, monitoring dashboards, and service-discovery tools that readenv.getRepository().getArtifacts()will show no listeners or services for those integrations.Fix
Introduce
isServiceType(BType)that recurses into union members and unwrapsBTypeReferenceTypebefore checkingFlags.SERVICE. Add asize() >= 2arity guard to protect bothgetFirst()and the runtimeargs[1]access:PRs
2201.13.x: Fix addServiceListener not injected for union service type in listener attach #44592 (merged), Guard against empty parameters and missing args[1] in isListenerAttach #44595 (arity guard follow-up)master: Fix addServiceListener not injected for union service type in listener attach #44594