Skip to content

Commit c3d24de

Browse files
authored
Fix SFn string lexing in Intrinsic Function arguments (#9783)
1 parent b4723ce commit c3d24de

File tree

10 files changed

+452
-129
lines changed

10 files changed

+452
-129
lines changed

localstack/services/stepfunctions/asl/antlr/ASLIntrinsicLexer.g4

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,11 +42,11 @@ UUID: 'UUID';
4242

4343

4444
STRING
45-
: '\'' (ESC | SAFECODEPOINT)* '\''
45+
: '\'' (ESC | SAFECODEPOINT)*? '\''
4646
;
4747

4848
fragment ESC
49-
: '\\' (["\\/bfnrt] | UNICODE)
49+
: '\\' (UNICODE | .)
5050
;
5151
fragment UNICODE
5252
: 'u' HEX HEX HEX HEX
@@ -55,7 +55,7 @@ fragment HEX
5555
: [0-9a-fA-F]
5656
;
5757
fragment SAFECODEPOINT
58-
: ~ ["\\\u0000-\u001F]
58+
: ~ ['\\\u0000-\u001F]
5959
;
6060
6161
INT

localstack/services/stepfunctions/asl/antlr/runtime/ASLIntrinsicLexer.interp

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.

localstack/services/stepfunctions/asl/antlr/runtime/ASLIntrinsicLexer.py

Lines changed: 125 additions & 125 deletions
Large diffs are not rendered by default.

localstack/services/stepfunctions/asl/parse/intrinsic/preprocessor.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import re
12
from typing import Optional
23

34
from antlr4.tree.Tree import ParseTree, TerminalNodeImpl
@@ -54,11 +55,21 @@
5455

5556

5657
class Preprocessor(ASLIntrinsicParserVisitor):
58+
@staticmethod
59+
def _replace_escaped_characters(match):
60+
escaped_char = match.group(1)
61+
if escaped_char.isalpha():
62+
replacements = {"n": "\n", "t": "\t", "r": "\r"}
63+
return replacements.get(escaped_char, escaped_char)
64+
else:
65+
return match.group(0)
66+
5767
@staticmethod
5868
def _text_of_str(parse_tree: ParseTree) -> str:
5969
pt = Antlr4Utils.is_production(parse_tree) or Antlr4Utils.is_terminal(parse_tree)
6070
inner_str = pt.getText()
6171
inner_str = inner_str[1:-1]
72+
inner_str = re.sub(r"\\(.)", Preprocessor._replace_escaped_characters, inner_str)
6273
return inner_str
6374

6475
def visitFunc_arg_int(self, ctx: ASLIntrinsicParser.Func_arg_intContext) -> FunctionArgumentInt:

tests/aws/services/stepfunctions/templates/intrinsicfunctions/intrinsic_functions_templates.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,3 +74,12 @@ class IntrinsicFunctionTemplate(TemplateLoader):
7474
FORMAT_CONTEXT_PATH: Final[str] = os.path.join(
7575
_THIS_FOLDER, "statemachines/generic/format_context_path.json5"
7676
)
77+
NESTED_CALLS_1: Final[str] = os.path.join(
78+
_THIS_FOLDER, "statemachines/generic/nested_calls_1.json5"
79+
)
80+
NESTED_CALLS_2: Final[str] = os.path.join(
81+
_THIS_FOLDER, "statemachines/generic/nested_calls_2.json5"
82+
)
83+
ESCAPE_SEQUENCE: Final[str] = os.path.join(
84+
_THIS_FOLDER, "statemachines/generic/escape_sequence.json5"
85+
)
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
{
2+
"Comment": "ESCAPE_SEQUENCE",
3+
"StartAt": "State0",
4+
"States": {
5+
"State0": {
6+
"Type": "Pass",
7+
"Parameters": {
8+
"FunctionResult.$": "States.StringSplit('Hello\nWorld', '\n')"
9+
},
10+
"End": true
11+
}
12+
}
13+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
{
2+
"Comment": "NESTED_CALLS_1",
3+
"StartAt": "State0",
4+
"States": {
5+
"State0": {
6+
"Type": "Pass",
7+
"Parameters": {
8+
"FunctionResult.$": "States.ArrayGetItem(States.StringSplit($$.StateMachine.Name, '-'), 0)"
9+
},
10+
"End": true
11+
}
12+
}
13+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
{
2+
"Comment": "NESTED_CALLS_2",
3+
"StartAt": "State0",
4+
"States": {
5+
"State0": {
6+
"Type": "Pass",
7+
"Parameters": {
8+
"FunctionResult.$": "States.ArrayGetItem(States.StringSplit($$.StateMachine.Name, '-'), States.MathAdd(States.ArrayLength(States.StringSplit($$.StateMachine.Name, '-')), -1))"
9+
},
10+
"End": true
11+
}
12+
}
13+
}

tests/aws/services/stepfunctions/v2/intrinsic_functions/test_generic.py

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,3 +65,45 @@ def test_context_json_path(
6565
IFT.FORMAT_CONTEXT_PATH,
6666
input_values,
6767
)
68+
69+
@markers.aws.validated
70+
def test_nested_calls_1(
71+
self, create_iam_role_for_sfn, create_state_machine, sfn_snapshot, aws_client
72+
):
73+
input_values = [None]
74+
create_and_test_on_inputs(
75+
aws_client.stepfunctions,
76+
create_iam_role_for_sfn,
77+
create_state_machine,
78+
sfn_snapshot,
79+
IFT.NESTED_CALLS_1,
80+
input_values,
81+
)
82+
83+
@markers.aws.validated
84+
def test_nested_calls_2(
85+
self, create_iam_role_for_sfn, create_state_machine, sfn_snapshot, aws_client
86+
):
87+
input_values = [None]
88+
create_and_test_on_inputs(
89+
aws_client.stepfunctions,
90+
create_iam_role_for_sfn,
91+
create_state_machine,
92+
sfn_snapshot,
93+
IFT.NESTED_CALLS_2,
94+
input_values,
95+
)
96+
97+
@markers.aws.validated
98+
def test_escape_sequence(
99+
self, create_iam_role_for_sfn, create_state_machine, sfn_snapshot, aws_client
100+
):
101+
input_values = [None]
102+
create_and_test_on_inputs(
103+
aws_client.stepfunctions,
104+
create_iam_role_for_sfn,
105+
create_state_machine,
106+
sfn_snapshot,
107+
IFT.ESCAPE_SEQUENCE,
108+
input_values,
109+
)

tests/aws/services/stepfunctions/v2/intrinsic_functions/test_generic.snapshot.json

Lines changed: 222 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1145,5 +1145,227 @@
11451145
}
11461146
}
11471147
}
1148+
},
1149+
"tests/aws/services/stepfunctions/v2/intrinsic_functions/test_generic.py::TestGeneric::test_nested_calls_1": {
1150+
"recorded-date": "30-11-2023, 17:29:44",
1151+
"recorded-content": {
1152+
"exec_hist_resp_0": {
1153+
"events": [
1154+
{
1155+
"executionStartedEventDetails": {
1156+
"input": {
1157+
"FunctionInput": null
1158+
},
1159+
"inputDetails": {
1160+
"truncated": false
1161+
},
1162+
"roleArn": "snf_role_arn"
1163+
},
1164+
"id": 1,
1165+
"previousEventId": 0,
1166+
"timestamp": "timestamp",
1167+
"type": "ExecutionStarted"
1168+
},
1169+
{
1170+
"id": 2,
1171+
"previousEventId": 0,
1172+
"stateEnteredEventDetails": {
1173+
"input": {
1174+
"FunctionInput": null
1175+
},
1176+
"inputDetails": {
1177+
"truncated": false
1178+
},
1179+
"name": "State0"
1180+
},
1181+
"timestamp": "timestamp",
1182+
"type": "PassStateEntered"
1183+
},
1184+
{
1185+
"id": 3,
1186+
"previousEventId": 2,
1187+
"stateExitedEventDetails": {
1188+
"name": "State0",
1189+
"output": {
1190+
"FunctionResult": "<ArnPart_0idx>"
1191+
},
1192+
"outputDetails": {
1193+
"truncated": false
1194+
}
1195+
},
1196+
"timestamp": "timestamp",
1197+
"type": "PassStateExited"
1198+
},
1199+
{
1200+
"executionSucceededEventDetails": {
1201+
"output": {
1202+
"FunctionResult": "<ArnPart_0idx>"
1203+
},
1204+
"outputDetails": {
1205+
"truncated": false
1206+
}
1207+
},
1208+
"id": 4,
1209+
"previousEventId": 3,
1210+
"timestamp": "timestamp",
1211+
"type": "ExecutionSucceeded"
1212+
}
1213+
],
1214+
"ResponseMetadata": {
1215+
"HTTPHeaders": {},
1216+
"HTTPStatusCode": 200
1217+
}
1218+
}
1219+
}
1220+
},
1221+
"tests/aws/services/stepfunctions/v2/intrinsic_functions/test_generic.py::TestGeneric::test_nested_calls_2": {
1222+
"recorded-date": "30-11-2023, 17:34:01",
1223+
"recorded-content": {
1224+
"exec_hist_resp_0": {
1225+
"events": [
1226+
{
1227+
"executionStartedEventDetails": {
1228+
"input": {
1229+
"FunctionInput": null
1230+
},
1231+
"inputDetails": {
1232+
"truncated": false
1233+
},
1234+
"roleArn": "snf_role_arn"
1235+
},
1236+
"id": 1,
1237+
"previousEventId": 0,
1238+
"timestamp": "timestamp",
1239+
"type": "ExecutionStarted"
1240+
},
1241+
{
1242+
"id": 2,
1243+
"previousEventId": 0,
1244+
"stateEnteredEventDetails": {
1245+
"input": {
1246+
"FunctionInput": null
1247+
},
1248+
"inputDetails": {
1249+
"truncated": false
1250+
},
1251+
"name": "State0"
1252+
},
1253+
"timestamp": "timestamp",
1254+
"type": "PassStateEntered"
1255+
},
1256+
{
1257+
"id": 3,
1258+
"previousEventId": 2,
1259+
"stateExitedEventDetails": {
1260+
"name": "State0",
1261+
"output": {
1262+
"FunctionResult": "<ArnPart_0idx>"
1263+
},
1264+
"outputDetails": {
1265+
"truncated": false
1266+
}
1267+
},
1268+
"timestamp": "timestamp",
1269+
"type": "PassStateExited"
1270+
},
1271+
{
1272+
"executionSucceededEventDetails": {
1273+
"output": {
1274+
"FunctionResult": "<ArnPart_0idx>"
1275+
},
1276+
"outputDetails": {
1277+
"truncated": false
1278+
}
1279+
},
1280+
"id": 4,
1281+
"previousEventId": 3,
1282+
"timestamp": "timestamp",
1283+
"type": "ExecutionSucceeded"
1284+
}
1285+
],
1286+
"ResponseMetadata": {
1287+
"HTTPHeaders": {},
1288+
"HTTPStatusCode": 200
1289+
}
1290+
}
1291+
}
1292+
},
1293+
"tests/aws/services/stepfunctions/v2/intrinsic_functions/test_generic.py::TestGeneric::test_escape_sequence": {
1294+
"recorded-date": "30-11-2023, 17:58:53",
1295+
"recorded-content": {
1296+
"exec_hist_resp_0": {
1297+
"events": [
1298+
{
1299+
"executionStartedEventDetails": {
1300+
"input": {
1301+
"FunctionInput": null
1302+
},
1303+
"inputDetails": {
1304+
"truncated": false
1305+
},
1306+
"roleArn": "snf_role_arn"
1307+
},
1308+
"id": 1,
1309+
"previousEventId": 0,
1310+
"timestamp": "timestamp",
1311+
"type": "ExecutionStarted"
1312+
},
1313+
{
1314+
"id": 2,
1315+
"previousEventId": 0,
1316+
"stateEnteredEventDetails": {
1317+
"input": {
1318+
"FunctionInput": null
1319+
},
1320+
"inputDetails": {
1321+
"truncated": false
1322+
},
1323+
"name": "State0"
1324+
},
1325+
"timestamp": "timestamp",
1326+
"type": "PassStateEntered"
1327+
},
1328+
{
1329+
"id": 3,
1330+
"previousEventId": 2,
1331+
"stateExitedEventDetails": {
1332+
"name": "State0",
1333+
"output": {
1334+
"FunctionResult": [
1335+
"Hello",
1336+
"World"
1337+
]
1338+
},
1339+
"outputDetails": {
1340+
"truncated": false
1341+
}
1342+
},
1343+
"timestamp": "timestamp",
1344+
"type": "PassStateExited"
1345+
},
1346+
{
1347+
"executionSucceededEventDetails": {
1348+
"output": {
1349+
"FunctionResult": [
1350+
"Hello",
1351+
"World"
1352+
]
1353+
},
1354+
"outputDetails": {
1355+
"truncated": false
1356+
}
1357+
},
1358+
"id": 4,
1359+
"previousEventId": 3,
1360+
"timestamp": "timestamp",
1361+
"type": "ExecutionSucceeded"
1362+
}
1363+
],
1364+
"ResponseMetadata": {
1365+
"HTTPHeaders": {},
1366+
"HTTPStatusCode": 200
1367+
}
1368+
}
1369+
}
11481370
}
11491371
}

0 commit comments

Comments
 (0)