@@ -1989,17 +1989,18 @@ int query_sexp_args_count(int node, bool only_valid_args = false)
19891989 return count;
19901990}
19911991
1992+ enum class ArgCountCheck { CORRECT, INCORRECT_BENIGN, INCORRECT_FATAL };
19921993/**
19931994 * Needed to fix bug with sexps like send-message list which have arguments that need to be supplied as a block
19941995 *
19951996 * @return whether the number of arguments for the supplied operation is correct
19961997 */
1997- static bool check_operator_argument_count(int count, int op_index)
1998+ static ArgCountCheck check_operator_argument_count(int count, int op_index)
19981999{
19992000 Assertion(op_index >= 0 && op_index < sz2i(Operators.size()), "op_index is out of range!");
20002001
20012002 if (count < Operators[op_index].min || count > Operators[op_index].max)
2002- return false ;
2003+ return ArgCountCheck::INCORRECT_FATAL ;
20032004
20042005 int op_const = Operators[op_index].value;
20052006
@@ -2011,9 +2012,9 @@ static bool check_operator_argument_count(int count, int op_index)
20112012
20122013 if (op_const == OP_SEND_MESSAGE_LIST || op_const == OP_SEND_MESSAGE_CHAIN)
20132014 if (count % 4 != 0)
2014- return false;
2015+ return ArgCountCheck::INCORRECT_BENIGN; // historically, this check didn't work at all, and sexps gracefully recovered at runtime
20152016
2016- return true ;
2017+ return ArgCountCheck::CORRECT ;
20172018}
20182019
20192020// helper functions for check_container_value_data_type()
@@ -2159,6 +2160,7 @@ int check_sexp_syntax(int node, int desired_return_type, int recursive, int *bad
21592160 int var_index = -1;
21602161 size_t st;
21612162 const sexp_container *p_container = nullptr; // for SEXPs that take container name as arg
2163+ int deferred_error = SEXP_CHECK_NO_ERROR, deferred_bad_node = -1; // for recoverable errors, so that the rest of the tree is still checked
21622164
21632165 Assertion(node >= 0 && node < Num_sexp_nodes, "Node %d must be a valid SEXP node!", node);
21642166 Assertion(Sexp_nodes[node].type != SEXP_NOT_USED, "Node %d must be in use!", node);
@@ -2207,8 +2209,15 @@ int check_sexp_syntax(int node, int desired_return_type, int recursive, int *bad
22072209
22082210 count = query_sexp_args_count(op_node);
22092211
2210- if (!check_operator_argument_count(sz2i(count), op_index))
2211- return SEXP_CHECK_BAD_ARG_COUNT; // incorrect number of arguments
2212+ auto arg_count_result = check_operator_argument_count(sz2i(count), op_index);
2213+ if (arg_count_result == ArgCountCheck::INCORRECT_FATAL)
2214+ return SEXP_CHECK_BAD_ARG_COUNT; // incorrect number of arguments
2215+ else if (arg_count_result == ArgCountCheck::INCORRECT_BENIGN)
2216+ {
2217+ // incorrect, but defer it and continue checking
2218+ deferred_error = SEXP_CHECK_BAD_ARG_COUNT_BENIGN;
2219+ deferred_bad_node = op_node;
2220+ }
22122221
22132222 node = Sexp_nodes[op_node].rest;
22142223 while (node != -1) {
@@ -2227,7 +2236,7 @@ int check_sexp_syntax(int node, int desired_return_type, int recursive, int *bad
22272236 // thing. (i.e. in the case of a cond statement, the conditional will fall into this if
22282237 // statement. MORE TO DO HERE!!!!
22292238 if (Sexp_nodes[i].subtype == SEXP_ATOM_LIST)
2230- return 0 ;
2239+ break ;
22312240
22322241 int op2_index = get_operator_index(i);
22332242 int op2_const = SCP_vector_inbounds(Operators, op2_index) ? Operators[op2_index].value : OP_NOT_AN_OP;
@@ -2242,7 +2251,13 @@ int check_sexp_syntax(int node, int desired_return_type, int recursive, int *bad
22422251 }
22432252
22442253 if ((z = check_sexp_syntax(i, (int)opr, recursive, bad_node)) != 0) {
2245- return z;
2254+ if (!sexp_recoverable_error(z))
2255+ return z;
2256+ // defer recoverable errors so that the rest of the tree is still checked
2257+ if (deferred_error == SEXP_CHECK_NO_ERROR) {
2258+ deferred_error = z;
2259+ deferred_bad_node = bad_node ? *bad_node : -1;
2260+ }
22462261 }
22472262 }
22482263
@@ -3153,7 +3168,13 @@ int check_sexp_syntax(int node, int desired_return_type, int recursive, int *bad
31533168 // we should check the syntax of the actual goal!!!!
31543169 z = Sexp_nodes[node].first;
31553170 if ((z = check_sexp_syntax(z, OPR_AI_GOAL, recursive, bad_node)) != 0){
3156- return z;
3171+ if (!sexp_recoverable_error(z))
3172+ return z;
3173+ // defer recoverable errors so that the rest of the tree is still checked
3174+ if (deferred_error == SEXP_CHECK_NO_ERROR) {
3175+ deferred_error = z;
3176+ deferred_bad_node = bad_node ? *bad_node : -1;
3177+ }
31573178 }
31583179
31593180 if (Fred_running) {
@@ -4211,6 +4232,14 @@ int check_sexp_syntax(int node, int desired_return_type, int recursive, int *bad
42114232 argnum++;
42124233 }
42134234
4235+ // now that the rest of the tree has been checked, report any recoverable error that was noted along the way
4236+ if (deferred_error != SEXP_CHECK_NO_ERROR)
4237+ {
4238+ if (bad_node)
4239+ *bad_node = deferred_bad_node;
4240+ return deferred_error;
4241+ }
4242+
42144243 return 0;
42154244}
42164245
@@ -35353,18 +35382,24 @@ bool sexp_recoverable_error(int num)
3535335382 // but the mission will run without crashing.
3535435383 case SEXP_CHECK_AMBIGUOUS_EVENT_NAME:
3535535384 case SEXP_CHECK_AMBIGUOUS_GOAL_NAME:
35385+ return true;
3535635386
3535735387 // Having an invalid gauge in FSO won't hurt,
3535835388 // as all places which call hud_get_gauge() or hud_get_custom_gauge() check its return value for NULL.
3535935389 case SEXP_CHECK_INVALID_CUSTOM_HUD_GAUGE:
3536035390 case SEXP_CHECK_INVALID_ANY_HUD_GAUGE:
35391+ return true;
3536135392
3536235393 // Trying to set an invalid sound environment has no effect, and all sound enviroments are invalid if EFX is disabled.
3536335394 // Invalid sound environment options are simiarly harmless.
3536435395 case SEXP_CHECK_INVALID_SOUND_ENVIRONMENT:
3536535396 case SEXP_CHECK_INVALID_SOUND_ENVIRONMENT_OPTION:
3536635397 return true;
3536735398
35399+ // Certain argument counts historically weren't checked properly, but the runtime code could still recover
35400+ case SEXP_CHECK_BAD_ARG_COUNT_BENIGN:
35401+ return true;
35402+
3536835403 // most errors will halt mission loading
3536935404 default:
3537035405 return false;
@@ -35387,6 +35422,7 @@ const char *sexp_error_message(int num)
3538735422 return "Argument type mismatch";
3538835423
3538935424 case SEXP_CHECK_BAD_ARG_COUNT:
35425+ case SEXP_CHECK_BAD_ARG_COUNT_BENIGN:
3539035426 return "Argument count is illegal";
3539135427
3539235428 case SEXP_CHECK_UNKNOWN_TYPE:
0 commit comments