knowledge(error-handling): a bare [TryFunction] call propagates its error

The review agent repeatedly flagged bare calls to [TryFunction] procedures
(e.g. the System Application "Xml Validation" Try* APIs) as defects,
including claims that the failure is "silently swallowed". A bare call is
an ordinary call: the error propagates as usual.

- Add negative knowledge bare-tryfunction-call-propagates-errors.md.
- Narrow ignored-tryfunction-return-disables-try-semantics to code that
  visibly expects the failure to be caught; the bad sample now shows that
  in code, and the good sample includes an intentional bare call as the
  clean control.
- Qualify "TryFunction catches all errors" in the events article and the
  "must be consumed" wording in the performance article.
- Error-handling leaf worklists both articles for bare [TryFunction] calls
  and requires the same evidence before flagging.
- Add the three paired articles to the evaluation overrides.
This commit is contained in:
Kilian Seizinger 2026-10-02 14:30:51 +02:00
parent ac249ba4c9
commit da5a28819e
8 changed files with 71 additions and 17 deletions

View file

@ -0,0 +1,31 @@
---
bc-version: [all]
domain: error-handling
keywords: [tryfunction, try-method, bare-call, error-propagation, swallowed-error, try-prefix, false-positive]
technologies: [al]
countries: [w1]
application-area: [all]
---
# A bare call to a [TryFunction] propagates its error; it is not a swallowed failure
## Description
A bare call to a `[TryFunction]` procedure propagates errors like any ordinary method call. When the caller ignores the Boolean result, the platform does not treat the invocation as a try-method call: an error raised inside it stops the caller exactly as an unattributed procedure would, and nothing is caught or converted to `false`. Calling a try-API this way — for example the System Application `Xml Validation` procedures `TrySetValidatedDocument`, `TryAddValidationSchema`, and `TryValidateAgainstSchema` — is therefore a legitimate way to let a validation error reach the user. Reviewers who know only that try methods "catch errors" misread such a call as a silent failure.
## Best Practice
Treat a bare call to a `[TryFunction]` as a throwing call. Do not report that its failure is swallowed, ignored, or invisible to the caller, and do not ask the author to capture the result only to re-raise it: when the error should propagate, the bare call already does so and keeps the original error text, code, and call stack. Recommend consuming the result only when the surrounding code visibly expects to continue past or handle the failure; that case is owned by `microsoft/knowledge/error-handling/ignored-tryfunction-return-disables-try-semantics.md`.
A `Try` name prefix is a convention, not a semantic. Resolve the called procedure's declaration before reasoning about its error behaviour. A `Try`-named procedure without `[TryFunction]` is an ordinary Boolean method; whether ignoring its result loses a failure depends on its body, not on its name.
## Anti Pattern
Review findings that flag a bare `[TryFunction]` call whose surrounding code and documentation intend the error to propagate, for example: "the Try-prefixed calls ignore their Boolean return value, so a parse or validation failure is silently swallowed", "the caller can never learn whether validation succeeded", or "the try semantics never activate, so errors escape as ordinary exceptions". The first two claims are false; the third describes the intended behaviour, not a defect.
Recommending `if not Try...() then Error(GetLastErrorText())` as the fix is part of the same false positive. It adds code, replaces the original error with a re-raised copy, and can move unsanitized customer content into the error message (see `microsoft/knowledge/privacy/getlasterrortext-customer-content-in-errors.md`).
## References
- [Handling errors using try methods](https://learn.microsoft.com/en-us/dynamics365/business-central/dev-itpro/developer/devenv-handling-errors-using-try-methods): "If a try method call doesn't use the return value, the try method operates like an ordinary method, and errors are exposed as usual."
- [System Application `Xml Validation` codeunit](https://github.com/microsoft/BCApps/blob/main/src/System%20Application/App/XML%20Validation/src/XmlValidation.Codeunit.al): every `Try*` procedure is declared `[TryFunction]`.

View file

@ -1,9 +1,13 @@
codeunit 50301 "Try Return Bad"
{
procedure ImportDocument()
procedure ImportDocument(): Boolean
begin
// Ignoring the Boolean result makes this an ordinary, throwing call.
// The bare call is not a try-method call: the error stops this procedure here,
// so the check below never sees it.
TryImportDocument();
if GetLastErrorText() <> '' then
exit(false);
exit(true);
end;
[TryFunction]

View file

@ -1,9 +1,17 @@
codeunit 50300 "Try Return Good"
{
procedure ImportDocument()
procedure ImportDocument(): Boolean
begin
// The caller continues on failure, so the result is consumed.
if not TryImportDocument() then
Error(ImportFailedErr);
exit(false);
exit(true);
end;
procedure ImportRequiredDocument()
begin
// The error should reach the user, so a bare call is correct.
TryImportDocument();
end;
[TryFunction]
@ -13,6 +21,5 @@ codeunit 50300 "Try Return Good"
end;
var
ImportFailedErr: Label 'The document could not be imported.';
SourceRejectedErr: Label 'The source document was rejected.';
}

View file

@ -1,30 +1,38 @@
---
bc-version: [13..]
domain: error-handling
keywords: [tryfunction, try-method, boolean-return, ignored-return-value, error-propagation]
keywords: [tryfunction, try-method, boolean-return, ignored-return-value, error-propagation, dead-failure-branch]
technologies: [al]
countries: [w1]
application-area: [all]
---
# Consume a TryFunction return value to enable try semantics
# Consume a TryFunction return value when the caller handles the failure
## Description
A procedure marked `[TryFunction]` catches errors only when the caller uses its Boolean return value. An assignment or conditional makes the invocation a try-method call; a bare call is treated as an ordinary procedure call and exposes errors as usual. The attribute alone does not make every invocation non-throwing.
A `[TryFunction]` catches errors only when the caller uses its Boolean return value. An assignment or conditional makes the invocation a try-method call; a bare call is treated as an ordinary procedure call and exposes errors as usual. The attribute alone does not make every invocation non-throwing, so code that handles the failure of a bare call never sees that failure: the error leaves the procedure before the handling runs.
## Best Practice
Consume the result directly: assign it to a Boolean or use the call in an `if` condition. Handle `false` immediately while the last-error state still describes that failure.
When the caller must continue, log, count, or translate the failure, consume the result directly: assign it to a Boolean or use the call in an `if` condition. Handle `false` immediately while the last-error state still describes that failure.
When the error should simply propagate, a bare call is correct and needs no change; see `microsoft/knowledge/error-handling/bare-tryfunction-call-propagates-errors.md`.
See sample: [`ignored-tryfunction-return-disables-try-semantics.good.al`](ignored-tryfunction-return-disables-try-semantics.good.al).
## Anti Pattern
Calling a `[TryFunction]` procedure as a standalone statement and assuming the attribute suppresses its errors. The call has ordinary error semantics because its Boolean result is ignored.
Calling a `[TryFunction]` procedure as a standalone statement while the surrounding code expects the failure to be caught. Detect it from code, not from the call shape alone: the bare call is followed by a `GetLastErrorText`, `GetLastErrorCode`, or `GetLastErrorObject` check, a failure branch, failure logging, or a `false`/failure result; or it sits in a loop that is meant to continue past failed items. None of that handling sees the call's error, because the error stops the procedure first.
A bare call with no such handling is intended propagation, not this anti-pattern.
See sample: [`ignored-tryfunction-return-disables-try-semantics.bad.al`](ignored-tryfunction-return-disables-try-semantics.bad.al).
## See also
`microsoft/knowledge/performance/use-tryfunction-for-error-catching-not-rollback.md` owns transaction rollback expectations after a try method has actually caught an error.
## References
- [Handling errors using try methods](https://learn.microsoft.com/en-us/dynamics365/business-central/dev-itpro/developer/devenv-handling-errors-using-try-methods): "If a try method call uses the return value in an `OK:=` statement or a conditional statement such as `if-then`, errors are caught."

View file

@ -11,7 +11,7 @@ application-area: [all]
## Description
A `TryFunction` catches all errors — including errors thrown by event subscribers. When an `[IntegrationEvent]` is raised inside a `TryFunction` body, any error a subscriber raises is silently swallowed by the TryFunction's error boundary. The subscriber's logic fails, the caller sees no error, and the calling code continues as if nothing happened. Subscribers have no way to signal failure to the caller.
A `TryFunction` whose caller consumes its Boolean result catches all errors raised during its execution — including errors thrown by event subscribers. When an `[IntegrationEvent]` is raised inside a `TryFunction` body, any error a subscriber raises is silently swallowed by the TryFunction's error boundary. The subscriber's logic fails, the caller sees no error, and the calling code continues as if nothing happened. Subscribers have no way to signal failure to the caller.
## Best Practice

View file

@ -29,4 +29,4 @@ See sample: [`use-tryfunction-for-error-catching-not-rollback.bad.al`](use-tryfu
## See also
`microsoft/knowledge/error-handling/ignored-tryfunction-return-disables-try-semantics.md` owns the separate call-site rule that a try method's Boolean result must be consumed.
`microsoft/knowledge/error-handling/ignored-tryfunction-return-disables-try-semantics.md` owns the separate call-site rule that only a call that consumes the Boolean result catches errors; `microsoft/knowledge/error-handling/bare-tryfunction-call-propagates-errors.md` records that a bare call propagates them as intended.