Bug 707050

Summary: "0 copy" terminates with stackunderflow if operand stack is empty
Product: Ghostscript Reporter: Martin Gieseking <martin.gieseking>
Component: PS InterpreterAssignee: Default assignee <ghostpdl-bugs>
Status: RESOLVED FIXED    
Severity: normal CC: alex
Priority: P4    
Version: 10.02.0   
Hardware: PC   
OS: Windows 11   
Customer: Word Size: ---

Description Martin Gieseking 2023-08-23 18:04:55 UTC
In GS 10.02.0RC1 the operation "0 copy" leads to a stackunderflow error if the operand stack is empty. I guess this isn't a new intended behavior.


GS>0 copy
Error: /stackunderflow in --copy--
Operand stack:   0
Execution stack:   %interp_exit   .runexec2   --nostringval--   --nostringval--   --nostringval--   2   %stopped_push   --nostringval--   --nostringval--   %loop_continue   --nostringval--   --nostringval--   false   1   %stopped_push   .runexec2   --nostringval--   --nostringval--   --nostringval--   2   %stopped_push   --nostringval--
Dictionary stack:   --dict:750/1123(ro)(G)--   --dict:0/20(G)--   --dict:85/200(L)--
Current allocation mode is local
Last OS error: No such file or directoryCurrent file position is 7
GS<1>
Comment 1 Ken Sharp 2023-08-23 18:12:57 UTC
Looks like correct behaviour to me, going by the PLRM. There should always be at least 2 operands to the copy operator and stackunderflow is both permitted and reasonable.

copy any1 … anyn n copy any1 … anyn any1 … anyn
     array1 array2 copy subarray2
     dict1 dict2 copy dict2
     string1 string2 copy substring2
     packedarray1 array2 copy subarray2
     gstate1 gstate2 copy gstate2


This is probably a result of much recent work relating to 'security' problems which has involved a lot of checking of operands and stack status. If previous behaviour differed I'd expect that to have been incorrect.

But I'm willing to be convinced otherwise if you can point to evidence that current behaviour is incorrect.
Comment 2 Martin Gieseking 2023-08-23 21:03:55 UTC
Thank you for the fast reply and the helpful information. I can't find any details about the correct behavior of "clear 0 copy". PLRM only mentions that n must be a non-negative integer, but how to interpret operand sequence any_1 ... any_n if n=0 is not further specified. In my opinion, both the former and the new implementation are therefore valid interpretations.

Is it really a necessary security enhancement if "0 copy" requires an additional (dummy) operand instead of just doing nothing? From a user perspective it's not hard to work around the new incompatibility but at the moment I can't see any advantages in changing the former behavior.
Comment 3 Alex Cherepanov 2023-08-23 21:31:03 UTC
Two other PS interpreters concur that "0 copy" is valid.
Comment 4 Alex Cherepanov 2023-08-23 21:47:34 UTC
Support of unreasonable edge cases is needed to make the code simple and reliable. For instance:

/double_the_stack {count copy} def
Comment 5 Ken Sharp 2023-08-24 06:53:53 UTC
(In reply to Martin Gieseking from comment #2)

> Is it really a necessary security enhancement if "0 copy" requires an
> additional (dummy) operand instead of just doing nothing? From a user
> perspective it's not hard to work around the new incompatibility but at the
> moment I can't see any advantages in changing the former behavior.

No I realised what you were driving at a couple of minutes after I turned my computer off, but at gone 7pm I didn't want to go back.

I'll look into it this morning, though why you would want to do s no-op like this escapes me, such a program would obviously be inefficient.
Comment 6 Martin Gieseking 2023-08-24 07:53:05 UTC
(In reply to Ken Sharp from comment #5)
> I'll look into it this morning, though why you would want to do s no-op like
> this escapes me, such a program would obviously be inefficient.

For my applications, I don't need it as a pure end in itself to do nothing but as a special case where a variable operand n can be 0, similar to Alex' example. With the new behavior, it's always necessary to check whether n is 0 and whether copy might fail due to an empty stack which breaks existing code and makes the use of copy more cumbersome.

To me, the specification in PLRM just says that "copy" needs n+1 operands if the top element is a non-negative integer. If there are less than n+1 elements on the stack, it terminates with /stackunderflow. After popping n, it then duplicates the n topmost elements. In case of n=0, it just does nothing but also doesn't require further elements to operate on. Thus, I don't see the need to check for  additional, unrelated elements on the stack. I also can't really read that as a hard requirement from the specification given in PLRM.
Comment 7 Ken Sharp 2023-08-24 08:20:38 UTC
This commit 9e64c191228b4c026d50fc6e1e4fd5f80d2df881 restores the previous behaviour

Thanks for testing the release candidate and taking the trouble to make a report!
Comment 8 Martin Gieseking 2023-08-24 08:41:04 UTC
Great! Thank you for having a look into this issue and for restoring the previous behavior.