Skip to content

addServiceListener not injected for union service types in listener attach codegen #44599

@anuruddhal

Description

@anuruddhal

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:

  1. 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 BUnionTypeisFlagOn(..., Flags.SERVICE) returns false, so injection is skipped.

  2. 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

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions