{"record":{"id":"d79d45bcae85848c","repo":"grpc/grpc-java","slug":"matcher-tree-depth-exceeds-limit-of-16","errorCode":null,"errorMessage":"Matcher tree depth exceeds limit of 16","messagePattern":"Matcher tree depth exceeds limit of 16","errorType":"validation","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"xds/src/main/java/io/grpc/xds/internal/matcher/UnifiedMatcher.java","lineNumber":69,"sourceCode":"    Matcher.OnMatch onNoMatch = proto.hasOnNoMatch() ? proto.getOnNoMatch() : null;\n    if (proto.hasMatcherList()) {\n      return new MatcherList(proto.getMatcherList(), onNoMatch, actionValidator);\n    } else if (proto.hasMatcherTree()) {\n      return new MatcherTree(proto.getMatcherTree(), onNoMatch, actionValidator);\n    }\n    return new NoOpMatcher(onNoMatch, actionValidator);\n  }\n\n  /**\n   * Parses a proto Matcher into a UnifiedMatcher, allowing all actions.\n   */\n  static UnifiedMatcher fromProto(Matcher proto) {\n    return fromProto(proto, (typeUrl) -> true);\n  }\n\n  private static void checkRecursionDepth(Matcher proto, int currentDepth) {\n    if (currentDepth > MAX_RECURSION_DEPTH) {\n      throw new IllegalArgumentException(\n          \"Matcher tree depth exceeds limit of \" + MAX_RECURSION_DEPTH);\n    }\n    if (proto.hasMatcherList()) {\n      for (Matcher.MatcherList.FieldMatcher fm : proto.getMatcherList().getMatchersList()) {\n        if (fm.hasOnMatch() && fm.getOnMatch().hasMatcher()) {\n          checkRecursionDepth(fm.getOnMatch().getMatcher(), currentDepth + 1);\n        }\n      }\n    } else if (proto.hasMatcherTree()) {\n      Matcher.MatcherTree tree = proto.getMatcherTree();\n      if (tree.hasExactMatchMap()) {\n        for (Matcher.OnMatch onMatch : tree.getExactMatchMap().getMapMap().values()) {\n          if (onMatch.hasMatcher()) {\n            checkRecursionDepth(onMatch.getMatcher(), currentDepth + 1);\n          }\n        }\n      } else if (tree.hasPrefixMatchMap()) {\n        for (Matcher.OnMatch onMatch : tree.getPrefixMatchMap().getMapMap().values()) {","sourceCodeStart":51,"sourceCodeEnd":87,"githubUrl":"https://github.com/grpc/grpc-java/blob/64daddc1f3d1975670f769f3e97bde8b2ba32d25/xds/src/main/java/io/grpc/xds/internal/matcher/UnifiedMatcher.java#L51-L87","documentation":"UnifiedMatcher.fromProto recursively converts an xDS Matcher proto into an in-process matcher tree. To prevent stack overflow / resource exhaustion from pathologically deep matcher configurations, checkRecursionDepth enforces MAX_RECURSION_DEPTH (16). If a matcher proto nests more than 16 levels of nested matchers (via matcher_list field matchers' on_match.matcher or on_no_match chains), IllegalArgumentException is thrown.","triggerScenarios":"Calling UnifiedMatcher.fromProto (directly or via xDS route/config parsing) with a Matcher proto whose nested on_match/on_no_match matcher chain is deeper than 16 levels; checkRecursionDepth increments depth per nested matcher and throws when currentDepth > MAX_RECURSION_DEPTH.","commonSituations":"Control planes (or hand-crafted envoy config) emitting deeply chained matchers; config generated by recursive tooling or nested per-route match policies; accidental infinite/near-infinite recursion in generated matcher protos from a misbehaving management server.","solutions":["Flatten or reduce the nesting depth of the Matcher proto so the on_match/on_no_match matcher chain is at most 16 levels deep","Inspect the xDS config from the management server (grpc-xds debug logging) and refactor deeply nested matchers into sequential/multiple matchers or a custom plugin","Raise MAX_RECURSION_DEPTH only if you control the build and understand stack-overflow risk — prefer reconfiguring instead","Guard on the control-plane side to validate matcher depth before sending config to clients"],"exampleFix":"// before: deeply nested generated matcher (17+ levels of on_match.matcher)\nMatcher root = /* 17-level nested chain from control plane */;\nUnifiedMatcher m = UnifiedMatcher.fromProto(root); // throws\n// after: restructure on control plane to nest <= 16 levels, or merge conditions\nMatcher root = flattenMatcherChain(proto, 16); // collapse middle layers\nUnifiedMatcher m = UnifiedMatcher.fromProto(root); // ok","handlingStrategy":"validation","validationCode":"// Count nested matcher depth before calling fromProto\nint depth(Matcher m) {\n  int max = 0;\n  if (m.hasMatcherList()) {\n    for (Matcher.MatcherList.FieldMatcher fm : m.getMatcherList().getMatchersList()) {\n      if (fm.hasOnMatch() && fm.getOnMatch().hasMatcher()) max = Math.max(max, 1 + depth(fm.getOnMatch().getMatcher()));\n    }\n  }\n  return max;\n}\nif (depth(proto) > 16) throw new IllegalArgumentException(\"xDS matcher too deep (max 16)\");","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Keep xDS matcher nesting under 16 levels on the control plane","Add config-time validation of matcher depth before sending to clients","Enable xDS config logging to spot deep matcher chains early"],"tags":["xds","configuration","recursion-limit","grpc"],"backgroundTag":"value-out-of-range","analyzedSha":"64daddc1f3d1975670f769f3e97bde8b2ba32d25","analyzedAt":"2026-09-08T06:14:57.704Z","contentChangedAt":"2026-09-08T06:14:57.704Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}