@@ -1257,12 +1257,32 @@ static bool DeletePromisedLinesMatching(EvalContext *ctx, Item **start, Item *be
12571257
12581258/********************************************************************/
12591259
1260+ static bool ReplaceBufferSafeWrite (char * line_buff , char * after , Buffer * replace , int start_off , int end_off ,
1261+ size_t after_size , size_t line_buff_size )
1262+ {
1263+ int needed ;
1264+ // Save portion of line after substitution:
1265+ needed = strlcpy (after , line_buff + end_off , after_size );
1266+ if (needed < 0 || (size_t ) needed >= after_size )
1267+ {
1268+ return false;
1269+ }
1270+ needed = snprintf (line_buff + start_off , line_buff_size - start_off ,
1271+ "%s%s" , BufferData (replace ), after );
1272+ if (needed < 0 || (size_t ) needed >= (line_buff_size - start_off ))
1273+ {
1274+ return false;
1275+ }
1276+ return true;
1277+ }
1278+
12601279static bool ReplacePatterns (EvalContext * ctx , Item * file_start , Item * file_end , const Attributes * a ,
12611280 const Promise * pp , EditContext * edcontext , PromiseResult * result )
12621281{
12631282 assert (a != NULL );
12641283 assert (pp != NULL );
12651284 assert (edcontext != NULL );
1285+ bool allow_non_convergent = PromiseGetConstraintAsBoolean (ctx , "allow_non_convergent" , pp );
12661286
12671287 char line_buff [CF_EXPANDSIZE ];
12681288 char after [CF_BUFSIZE ];
@@ -1313,14 +1333,13 @@ static bool ReplacePatterns(EvalContext *ctx, Item *file_start, Item *file_end,
13131333 Log (LOG_LEVEL_VERBOSE , "Verifying replacement of '%s' with '%s', cutoff %d" , pp -> promiser , BufferData (replace ),
13141334 cutoff );
13151335
1316- // Save portion of line after substitution:
1317- strlcpy (after , line_buff + end_off , sizeof (after ));
1318- // TODO: gripe if that truncated !
1319-
1320- // Substitute into line_buff:
1321- snprintf (line_buff + start_off , sizeof (line_buff ) - start_off ,
1322- "%s%s" , BufferData (replace ), after );
1323- // TODO: gripe if that truncated or failed !
1336+ if (!ReplaceBufferSafeWrite (line_buff , after , replace , start_off , end_off , sizeof (after ), sizeof (line_buff )))
1337+ {
1338+ RecordInterruption (ctx , pp , a , "Replacement string is too large. '%s' in '%s'" ,
1339+ a -> column .column_separator , edcontext -> filename );
1340+ * result = PromiseResultUpdate (* result , PROMISE_RESULT_INTERRUPTED );
1341+ break ;
1342+ }
13241343 notfound = false;
13251344 replaced = true;
13261345
@@ -1330,17 +1349,40 @@ static bool ReplacePatterns(EvalContext *ctx, Item *file_start, Item *file_end,
13301349 break ;
13311350 }
13321351 }
1333-
1352+ char line_buff_cp [ CF_EXPANDSIZE ];
13341353 if (NotAnchored (pp -> promiser ) && BlockTextMatch (ctx , pp -> promiser , line_buff , & start_off , & end_off ))
13351354 {
1336- RecordInterruption (ctx , pp , a ,
1337- "Promised replacement '%s' on line '%s' for pattern '%s'"
1338- " is not convergent while editing '%s'"
1339- " (regular expression matches the replacement string)" ,
1340- line_buff , ip -> name , pp -> promiser , edcontext -> filename );
1341- * result = PromiseResultUpdate (* result , PROMISE_RESULT_INTERRUPTED );
1342- PromiseRef (LOG_LEVEL_ERR , pp );
1343- break ;
1355+ int needed = strlcpy (line_buff_cp , line_buff , sizeof (line_buff_cp ));
1356+ if (needed < 0 || (size_t ) needed >= sizeof (line_buff_cp ))
1357+ {
1358+ RecordInterruption (ctx , pp , a , "Original string is too large. '%s' in '%s'" ,
1359+ a -> column .column_separator , edcontext -> filename );
1360+ * result = PromiseResultUpdate (* result , PROMISE_RESULT_INTERRUPTED );
1361+ break ;
1362+ }
1363+
1364+ if (!ReplaceBufferSafeWrite (line_buff_cp , after , replace , start_off , end_off , sizeof (after ), sizeof (line_buff_cp )))
1365+ {
1366+ RecordInterruption (ctx , pp , a ,
1367+ "Promised replacement '%s' on line '%s' for pattern '%s'"
1368+ " is not convergent while editing '%s'"
1369+ " (regular expression matches the replacement string)" ,
1370+ line_buff , ip -> name , pp -> promiser , edcontext -> filename );
1371+ * result = PromiseResultUpdate (* result , PROMISE_RESULT_INTERRUPTED );
1372+ break ;
1373+ }
1374+
1375+ if (!allow_non_convergent || (strlen (line_buff ) != strlen (line_buff_cp )))
1376+ {
1377+ RecordInterruption (ctx , pp , a ,
1378+ "Promised replacement '%s' on line '%s' for pattern '%s'"
1379+ " is not convergent while editing '%s'"
1380+ " (regular expression matches the replacement string)" ,
1381+ line_buff , ip -> name , pp -> promiser , edcontext -> filename );
1382+ * result = PromiseResultUpdate (* result , PROMISE_RESULT_INTERRUPTED );
1383+ PromiseRef (LOG_LEVEL_ERR , pp );
1384+ break ;
1385+ }
13441386 }
13451387
13461388 if (!MakingChanges (ctx , pp , a , result , "replace pattern '%s' in '%s'" , pp -> promiser ,
@@ -1366,8 +1408,25 @@ static bool ReplacePatterns(EvalContext *ctx, Item *file_start, Item *file_end,
13661408 break ;
13671409 }
13681410
1369- if (BlockTextMatch (ctx , pp -> promiser , ip -> name , & start_off , & end_off ))
1411+ if (BlockTextMatch (ctx , pp -> promiser , ip -> name , & start_off , & end_off ) && (!allow_non_convergent
1412+ || (strlen (line_buff ) != strlen (line_buff_cp ))))
13701413 {
1414+ int needed = strlcpy (line_buff_cp , line_buff , sizeof (line_buff_cp ));
1415+ if (needed < 0 || (size_t ) needed >= sizeof (line_buff_cp ))
1416+ {
1417+ RecordInterruption (ctx , pp , a , "Original string is too large. '%s' in '%s'" ,
1418+ a -> column .column_separator , edcontext -> filename );
1419+ * result = PromiseResultUpdate (* result , PROMISE_RESULT_INTERRUPTED );
1420+ break ;
1421+ }
1422+
1423+ if (!ReplaceBufferSafeWrite (line_buff_cp , after , replace , start_off , end_off , sizeof (after ), sizeof (line_buff_cp ))) {
1424+ RecordInterruption (ctx , pp , a , "Replacement string is too large. '%s' in '%s'" ,
1425+ a -> column .column_separator , edcontext -> filename );
1426+ * result = PromiseResultUpdate (* result , PROMISE_RESULT_INTERRUPTED );
1427+ break ;
1428+ }
1429+
13711430 RecordInterruption (ctx , pp , a ,
13721431 "Promised replacement '%s' for pattern '%s' is not properly convergent while editing '%s'"
13731432 " (pattern still matches the end-state replacement string '%s', consider use"
0 commit comments