-
Notifications
You must be signed in to change notification settings - Fork 1.2k
fix(intrinsics): only resolve the selected Fn::If branch #9134
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Changes from 6 commits
d4c07ac
16c70d0
e16ec5b
0a7b27d
88ffaea
57b4fbd
b63826a
ace7482
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -716,24 +716,14 @@ def handle_fn_if(self, intrinsic_value, ignore_errors): | |
| ------- | ||
| This will return value_if_true and value_if_false depending on how the condition is evaluated | ||
| """ | ||
| arguments = self.intrinsic_property_resolver( | ||
| intrinsic_value, ignore_errors, parent_function=IntrinsicResolver.FN_IF | ||
| ) | ||
| verify_intrinsic_type_list(arguments, IntrinsicResolver.FN_IF) | ||
| verify_number_arguments(arguments, IntrinsicResolver.FN_IF, num=3) | ||
| verify_intrinsic_type_list(intrinsic_value, IntrinsicResolver.FN_IF) | ||
| verify_number_arguments(intrinsic_value, IntrinsicResolver.FN_IF, num=3) | ||
|
|
||
| condition_name = self.intrinsic_property_resolver( | ||
| arguments[0], ignore_errors, parent_function=IntrinsicResolver.FN_IF | ||
| intrinsic_value[0], ignore_errors, parent_function=IntrinsicResolver.FN_IF | ||
| ) | ||
| verify_intrinsic_type_str(condition_name, IntrinsicResolver.FN_IF) | ||
|
|
||
| value_if_true = self.intrinsic_property_resolver( | ||
| arguments[1], ignore_errors, parent_function=IntrinsicResolver.FN_IF | ||
| ) | ||
| value_if_false = self.intrinsic_property_resolver( | ||
| arguments[2], ignore_errors, parent_function=IntrinsicResolver.FN_IF | ||
| ) | ||
|
|
||
| condition = self._conditions.get(condition_name) | ||
| verify_intrinsic_type_dict( | ||
| condition, | ||
|
|
@@ -750,7 +740,8 @@ def handle_fn_if(self, intrinsic_value, ignore_errors): | |
| message="The result of {} must evaluate to bool".format(IntrinsicResolver.FN_IF), | ||
| ) | ||
|
|
||
| return value_if_true if condition_evaluated else value_if_false | ||
| selected_value = intrinsic_value[1] if condition_evaluated else intrinsic_value[2] | ||
| return self.intrinsic_property_resolver(selected_value, ignore_errors, parent_function=IntrinsicResolver.FN_IF) | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [BUG] When the selected branch is # intrinsic_property_resolver, dict branch
sanitized_val = self.intrinsic_property_resolver(val, ignore_errors, parent_function=parent_function)
...
sanitized_dict[sanitized_key] = sanitized_val # key kept, value is NoneThe key is retained rather than dropped, which does not match CloudFormation's Events:
ApiEvent: !If [UseApi, {Type: Api, Properties: {Path: /, Method: get}}, !Ref "AWS::NoValue"]With for , event in serverlessfunction_events.items():
event_type = event.get(self._EVENT_TYPE) # AttributeError: 'NoneType' object has no attribute 'get'Previously that entry stayed as the raw Normalizing at the resolver would fix the whole class at once, and there is already precedent for exactly this in the codebase —
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fixed |
||
|
|
||
| def handle_fn_equals(self, intrinsic_value, ignore_errors): | ||
| """ | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[BUG] handle_fn_if can now return
None, which it could never do before, and at least one downstream consumer is not null-safe.Before this change, a branch containing
!Ref AWS::NoValuewas pre-resolved toNoneby the outerintrinsic_property_resolver(intrinsic_value, ...)call, and the subsequentresolve(arguments[1|2])hit theif intrinsic is None: raise InvalidIntrinsicExceptionguard at the top ofintrinsic_property_resolver. SoFn::Ifalways raised, and withignore_errors=Truethe enclosing dict loop left the property as the raw{"Fn::If": [...]}dict.Now the selected branch is resolved directly, so
!Ref AWS::NoValuereachesIntrinsicsSymbolTable.handle_pseudo_no_value()andNoneis returned and assigned as the property value (the generic dict branch doessanitized_dict[sanitized_key] = sanitized_valwith no None filtering).Concrete failure — the common "conditionally omit a property" idiom:
When
UseLayersis false,Properties["Layers"]becomesNone. Insamcli/lib/providers/sam_function_provider.py:265,resource_properties.get("Layers", [])returnsNone(the default only applies when the key is absent), and_parse_layer_infothen doesfor layer in list_of_layers→TypeError: 'NoneType' object is not iterable, an unhandled traceback instead of a domain error.Note the element-level form
Layers: [!If [Cond, !Ref MyLayer, !Ref "AWS::NoValue"]]is fine — it yields[None]and_parse_layer_infoskips unrecognized entries. Only the whole-property form breaks, and that is exactly one of the scenarios this PR sets out to fix, so it is worth closing here rather than leaving it as a newly reachable crash.Two options:
AWS::NoValuesemantics by dropping keys whose resolved value isNonein the generic dict branch ofintrinsic_property_resolver. This is the semantically correct fix but has wider blast radius, so it needs its own tests.resource_properties.get("Layers") or []in both call sites insam_function_provider.py.Either way, please add a unit test covering an
Fn::Ifwhose selected branch is!Ref AWS::NoValue— the four new tests only cover unresolvableFn::GetAttin the unselected branch, so this path is currently untested.Note on the previous review comment: the handle_fn_getatt change that forwarded ignore_errors into resolve_symbols is no longer in the diff, and test_template_ignore_errors_leaves_unresolvable_layer_getatt_as_dict was added as a guard for the nested-stack layer behavior. That finding is resolved and I did not re-raise it. The PR description still describes the handle_fn_getatt change, so it is now out of date.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed
b63826a