The poor-man's version of Set-Next-Statement (aka, SetIp) is to just forcibly set the instruction pointer register (eip on x86) to the instruction you want to execute next. However, this naive approach has several problems that ICorDebug's SetIp solves: 
- the naive approach doesn't update variable homes. For example, suppose local variable 'x' starts off in ecx and then gets spilled to the stack. If you just shift the eip, you won't update the local variables to be in their new homes. ICorDebug bends over backwards to remap locals from the old variable homes to the new ones when you set-next-statement.
- SetIp must play well with other things in the function, particularly any sort of stack operations like localloc or exception handling. 

Failure cases:
In ICorDebug, if SetIp can't properly handle these things, it fails the operation. This often requires additional information not tracked in optimized code, which is why managed setip often fails in optimized code. ICorDebug exposes many SetIp failure codes (all defined in corerror.h):

CORDBG_S_BAD_START_SEQUENCE_POINT
CORDBG_S_BAD_END_SEQUENCE_POINT
CORDBG_S_INSUFFICIENT_INFO_FOR_SET_IP
CORDBG_E_CANT_SET_IP_INTO_FINALLY
CORDBG_E_CANT_SET_IP_OUT_OF_FINALLY
CORDBG_E_CANT_SET_IP_INTO_CATCH
CORDBG_E_SET_IP_NOT_ALLOWED_ON_NONLEAF_FRAME
CORDBG_E_SET_IP_IMPOSSIBLE
CORDBG_E_CANT_SETIP_INTO_OR_OUT_OF_FILTER
CORDBG_E_CANT_SET_IP_OUT_OF_FINALLY_ON_WIN64
CORDBG_E_CANT_SET_IP_OUT_OF_CATCH_ON_WIN64
CORDBG_E_SET_IP_NOT_ALLOWED_ON_EXCEPTION
 

It's another design issue whether we should have exposed so many different error codes. This is probably more detail than the user needs; and some of these are pretty implementation specific. The bright side is that this enables a debugger to provide a detailed error message about why SetIp may not be allowed. We have a similar issue with func-eval error codes.

Doing SetIp at the ICorDebug level:
In ICorDebug, SetIp is exposed off the ICorDebug*Frame interfaces. You can set IP based off either IL or native offsets. ICD exposes two methods: SetIp and CanSetIp. CanSetIp lets a debugger query if SetIp would succeed without actually doing the SetIp. This can be used in UI for the end-user. For example, Visual Studio lets you SetIp by dragging the current line arrow (the yellow thing). It can then change the cursor icon if the target is an invalid SetIp destination.It can also use CanSetIp to determine whether it should include a "Set Next Statement" option in a context menu.