Avoid evaluating Vim globals in Python

On Python 3, evaluating a Vim expression will raise a unicode exception
if it contains an invalid sequence of bytes for the current encoding.
We can't really do anything about it because this is the way Vim and
Python 3 interact. However, we can prevent this situation to occur by
not evaluating Vim data that we have no control over: in particular,
the Vim globals. This is done by:
 - adding one by one the YCM default options instead of extending the
   Vim globals with them;
 - only evaluating the Vim global variable names (and not their values)
   when building the YCM options for the ycmd server.
This commit is contained in:
micbou 2016-04-30 18:14:48 +02:00
parent 87f0f7e16d
commit b923431d7d
2 changed files with 14 additions and 35 deletions

View File

@ -35,17 +35,16 @@ YCM_VAR_PREFIX = 'ycm_'
def BuildServerConf(): def BuildServerConf():
"""Builds a dictionary mapping YCM Vim user options to values. Option names """Builds a dictionary mapping YCM Vim user options to values. Option names
don't have the 'ycm_' prefix.""" don't have the 'ycm_' prefix."""
# We only evaluate the keys of the vim globals and not the whole dictionary
vim_globals = vimsupport.GetReadOnlyVimGlobals( force_python_objects = True ) # to avoid unicode issues.
# See https://github.com/Valloric/YouCompleteMe/pull/2151 for details.
keys = vimsupport.GetVimGlobalsKeys()
server_conf = {} server_conf = {}
for key, value in iteritems( vim_globals ): for key in keys:
if not key.startswith( YCM_VAR_PREFIX ): if not key.startswith( YCM_VAR_PREFIX ):
continue continue
try:
new_value = int( value )
except:
new_value = value
new_key = key[ len( YCM_VAR_PREFIX ): ] new_key = key[ len( YCM_VAR_PREFIX ): ]
new_value = vimsupport.VimExpressionToPythonType( 'g:' + key )
server_conf[ new_key ] = new_value server_conf[ new_key ] = new_value
return server_conf return server_conf
@ -53,11 +52,10 @@ def BuildServerConf():
def LoadJsonDefaultsIntoVim(): def LoadJsonDefaultsIntoVim():
defaults = user_options_store.DefaultOptions() defaults = user_options_store.DefaultOptions()
vim_defaults = {}
for key, value in iteritems( defaults ): for key, value in iteritems( defaults ):
vim_defaults[ 'ycm_' + key ] = value new_key = 'g:ycm_' + key
if not vimsupport.VariableExists( new_key ):
vimsupport.LoadDictIntoVimGlobals( vim_defaults, overwrite = False ) vimsupport.SetVariableValue( new_key, value )
def CompletionStartColumn(): def CompletionStartColumn():

View File

@ -294,27 +294,8 @@ def ConvertDiagnosticsToQfList( diagnostics ):
return [ ConvertDiagnosticToQfFormat( x ) for x in diagnostics ] return [ ConvertDiagnosticToQfFormat( x ) for x in diagnostics ]
# Given a dict like {'a': 1}, loads it into Vim as if you ran 'let g:a = 1' def GetVimGlobalsKeys():
# When |overwrite| is True, overwrites the existing value in Vim. return vim.eval( 'keys( g: )' )
def LoadDictIntoVimGlobals( new_globals, overwrite = True ):
extend_option = '"force"' if overwrite else '"keep"'
# We need to use json.dumps because that won't use the 'u' prefix on strings
# which Vim would bork on.
vim.eval( 'extend( g:, {0}, {1})'.format( json.dumps( new_globals ),
extend_option ) )
# Changing the returned dict will NOT change the value in Vim.
def GetReadOnlyVimGlobals( force_python_objects = False ):
if force_python_objects:
return vim.eval( 'g:' )
try:
# vim.vars is fairly new so it might not exist
return vim.vars
except:
return vim.eval( 'g:' )
def VimExpressionToPythonType( vim_expression ): def VimExpressionToPythonType( vim_expression ):
@ -491,8 +472,8 @@ def EchoTextVimWidth( text ):
EchoText( truncated_text, False ) EchoText( truncated_text, False )
vim.command( 'let &ruler = {0}'.format( old_ruler ) ) SetVariableValue( '&ruler', old_ruler )
vim.command( 'let &showcmd = {0}'.format( old_showcmd ) ) SetVariableValue( '&showcmd', old_showcmd )
def EscapeForVim( text ): def EscapeForVim( text ):
@ -514,7 +495,7 @@ def VariableExists( variable ):
def SetVariableValue( variable, value ): def SetVariableValue( variable, value ):
vim.command( "let {0} = '{1}'".format( variable, EscapeForVim( value ) ) ) vim.command( "let {0} = {1}".format( variable, json.dumps( value ) ) )
def GetVariableValue( variable ): def GetVariableValue( variable ):