mirror of
https://github.com/eclipse-cdt/cdt
synced 2025-07-24 01:15:29 +02:00
Bug 558809: Handle cases where Oomph corrupts \0 char in preference
Some CDT preferences use \0 as a separator in preferences. Somewhere in the Oomph preference synchronizer stack there is, or was, a place that failed to escape/unescape preferences with encoded \0 properly. CDT would then fail to parse the preference and an exception would be raised, causing code completions and the editor to be broken. This patch hardens the CDT code to: (1) Allow an escaped \0 to be used as a separator on read (Oomph uses ${0x0}) (2) Handle NumberFormatExceptions gracefully. In this case that means showing user a pop-up that their completion preferences are empty and offering to reset them, or edit them in preference page. This UI logic already existed, so all the new code has to do on failed parse is return a list of all disabled completions. Change-Id: Ibf3b05c0855bb96c195ca43139a50c27a2a90c7e
This commit is contained in:
parent
cb4c20c6ab
commit
7818f6e494
5 changed files with 206 additions and 32 deletions
|
@ -0,0 +1,81 @@
|
|||
/*******************************************************************************
|
||||
* Copyright (c) 2020 Kichwa Coders Canada Inc. and others.
|
||||
*
|
||||
* This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License 2.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* https://www.eclipse.org/legal/epl-2.0/
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.eclipse.cdt.ui.tests.text.contentassist;
|
||||
|
||||
import static java.util.Arrays.asList;
|
||||
import static org.eclipse.cdt.internal.ui.text.contentassist.CompletionProposalComputerPreferenceParser.parseCategoryOrder;
|
||||
import static org.eclipse.cdt.internal.ui.text.contentassist.CompletionProposalComputerPreferenceParser.parseExcludedCategories;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import java.text.ParseException;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
public class CompletionProposalComputerPreferenceParserTest {
|
||||
|
||||
@Test
|
||||
public void testParseExcludedCategories() throws ParseException {
|
||||
assertEquals(asSet(), parseExcludedCategories("\0"));
|
||||
assertEquals(asSet(), parseExcludedCategories("\0\0"));
|
||||
assertEquals(asSet("cat1"), parseExcludedCategories("cat1\0"));
|
||||
assertEquals(asSet("cat1", "cat2"), parseExcludedCategories("cat1\0cat2"));
|
||||
assertEquals(asSet("cat1"), parseExcludedCategories("cat1${0x0}"));
|
||||
assertEquals(asSet("cat1", "cat2"), parseExcludedCategories("cat1${0x0}cat2"));
|
||||
assertEquals(asSet("cat1"), parseExcludedCategories("cat1$$$${0x0}"));
|
||||
assertEquals(asSet("cat1", "cat2"), parseExcludedCategories("cat1$$$${0x0}cat2"));
|
||||
assertEquals(asSet("cat1", "cat2", "cat3", "cat4"),
|
||||
parseExcludedCategories("cat1$$$${0x0}cat2${0x0}cat3\0cat4\0"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParseCategoryOrder() throws ParseException {
|
||||
assertEquals(asMap("cat1", 1), parseCategoryOrder("cat1:1\0"));
|
||||
assertEquals(asMap("cat1", 1000), parseCategoryOrder("cat1:1000\0\0"));
|
||||
assertEquals(asMap("cat1", 1000, "cat2", 2000), parseCategoryOrder("cat1:1000\0cat2:2000"));
|
||||
assertEquals(asMap("cat1", 1), parseCategoryOrder("cat1:1${0x0}"));
|
||||
assertEquals(asMap("cat1", 1000, "cat2", 2000), parseCategoryOrder("cat1:1000${0x0}cat2:2000"));
|
||||
assertEquals(asMap("cat1", 1), parseCategoryOrder("cat1:1$$$$${0x0}"));
|
||||
assertEquals(asMap("cat1", 1000, "cat2", 2000), parseCategoryOrder("cat1:1000$$$$${0x0}cat2:2000"));
|
||||
assertEquals(asMap("cat1", 1000, "cat2", 2000, "cat3", 3000, "cat4", 4000),
|
||||
parseCategoryOrder("cat1:1000$$$$${0x0}cat2:2000${0x0}cat3:3000\0cat4:4000\0"));
|
||||
}
|
||||
|
||||
@Test(expected = ParseException.class)
|
||||
public void testParseIntFailsGracefully1() throws ParseException {
|
||||
assertEquals(asMap(), parseCategoryOrder("cat1:this is not a number\0"));
|
||||
}
|
||||
|
||||
@Test(expected = ParseException.class)
|
||||
public void testParseIntFailsGracefully2() throws ParseException {
|
||||
assertEquals(asMap(), parseCategoryOrder("cat1 missing number\0"));
|
||||
}
|
||||
|
||||
@Test(expected = ParseException.class)
|
||||
public void testParseIntFailsGracefully3() throws ParseException {
|
||||
assertEquals(asMap(), parseCategoryOrder("cat1:0:extra field\0"));
|
||||
}
|
||||
|
||||
private Set<String> asSet(String... elem) {
|
||||
return new HashSet<>(asList(elem));
|
||||
}
|
||||
|
||||
private Map<String, Integer> asMap(Object... elem) {
|
||||
HashMap<String, Integer> map = new HashMap<>();
|
||||
for (int i = 0; i < elem.length; i += 2) {
|
||||
map.put((String) elem[i + 0], (Integer) elem[i + 1]);
|
||||
}
|
||||
return map;
|
||||
}
|
||||
}
|
|
@ -22,6 +22,8 @@ import org.junit.runners.Suite;
|
|||
|
||||
ContentAssistTests.class,
|
||||
|
||||
CompletionProposalComputerPreferenceParserTest.class,
|
||||
|
||||
})
|
||||
public class ContentAssistTestSuite {
|
||||
}
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
*******************************************************************************/
|
||||
package org.eclipse.cdt.internal.ui.preferences;
|
||||
|
||||
import java.text.ParseException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
|
@ -21,10 +22,12 @@ import java.util.HashMap;
|
|||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.eclipse.cdt.internal.ui.dialogs.IStatusChangeListener;
|
||||
import org.eclipse.cdt.internal.ui.dialogs.StatusInfo;
|
||||
import org.eclipse.cdt.internal.ui.text.contentassist.CompletionProposalCategory;
|
||||
import org.eclipse.cdt.internal.ui.text.contentassist.CompletionProposalComputerPreferenceParser;
|
||||
import org.eclipse.cdt.internal.ui.text.contentassist.CompletionProposalComputerRegistry;
|
||||
import org.eclipse.cdt.internal.ui.util.Messages;
|
||||
import org.eclipse.cdt.internal.ui.util.SWTUtil;
|
||||
|
@ -230,22 +233,25 @@ final class CodeAssistAdvancedConfigurationBlock extends OptionsConfigurationBlo
|
|||
}
|
||||
|
||||
private boolean readInclusionPreference(CompletionProposalCategory cat) {
|
||||
String[] ids = getTokens(getValue(PREF_EXCLUDED_CATEGORIES), SEPARATOR);
|
||||
for (String id : ids) {
|
||||
if (id.equals(cat.getId()))
|
||||
return false;
|
||||
String value = getValue(PREF_EXCLUDED_CATEGORIES);
|
||||
try {
|
||||
Set<String> parseExcludedCategories = CompletionProposalComputerPreferenceParser
|
||||
.parseExcludedCategories(value);
|
||||
return !parseExcludedCategories.contains(cat.getId());
|
||||
} catch (ParseException e) {
|
||||
return true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private int readOrderPreference(CompletionProposalCategory cat) {
|
||||
String[] sortOrderIds = getTokens(getValue(PREF_CATEGORY_ORDER), SEPARATOR);
|
||||
for (String sortOrderId : sortOrderIds) {
|
||||
String[] idAndRank = getTokens(sortOrderId, COLON);
|
||||
if (idAndRank[0].equals(cat.getId()))
|
||||
return Integer.parseInt(idAndRank[1]);
|
||||
String categoryOrderPref = getValue(PREF_CATEGORY_ORDER);
|
||||
try {
|
||||
Map<String, Integer> parseCategoryOrder = CompletionProposalComputerPreferenceParser
|
||||
.parseCategoryOrder(categoryOrderPref);
|
||||
return parseCategoryOrder.getOrDefault(cat.getId(), LIMIT + 1);
|
||||
} catch (ParseException e) {
|
||||
return LIMIT + 1;
|
||||
}
|
||||
return LIMIT + 1;
|
||||
}
|
||||
|
||||
public void update() {
|
||||
|
|
|
@ -0,0 +1,78 @@
|
|||
/*******************************************************************************
|
||||
* Copyright (c) 2020 Kichwa Coders Canada Inc. and others.
|
||||
*
|
||||
* This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License 2.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* https://www.eclipse.org/legal/epl-2.0/
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.eclipse.cdt.internal.ui.text.contentassist;
|
||||
|
||||
import java.text.ParseException;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.eclipse.cdt.ui.PreferenceConstants;
|
||||
|
||||
/**
|
||||
* Parses the Completion Proposal Computer Preferences.
|
||||
* <p>
|
||||
* See org.eclipse.cdt.internal.ui.preferences.CodeAssistAdvancedConfigurationBlock.PreferenceModel
|
||||
* for the write side of the preferences.
|
||||
*/
|
||||
public class CompletionProposalComputerPreferenceParser {
|
||||
/**
|
||||
* Parses the {@link PreferenceConstants#CODEASSIST_EXCLUDED_CATEGORIES} value and
|
||||
* converts to a set of categories that are excluded.
|
||||
* @param preferenceValue as stored in {@link PreferenceConstants#CODEASSIST_EXCLUDED_CATEGORIES}
|
||||
* @return set of excluded categories
|
||||
* @throws ParseException if the value cannot be parsed
|
||||
*/
|
||||
public static Set<String> parseExcludedCategories(String preferenceValue) throws ParseException {
|
||||
Set<String> disabled = new HashSet<>();
|
||||
String[] disabledArray = splitOnNulls(preferenceValue);
|
||||
disabled.addAll(Arrays.asList(disabledArray));
|
||||
return disabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the {@link PreferenceConstants#CODEASSIST_CATEGORY_ORDER} value and
|
||||
* converts to a map of category ids to sort rank number
|
||||
* @param preferenceValue as stored in {@link PreferenceConstants#CODEASSIST_CATEGORY_ORDER}
|
||||
* @return map of category id to rank order
|
||||
* @throws ParseException if the value cannot be parsed
|
||||
*/
|
||||
public static Map<String, Integer> parseCategoryOrder(String preferenceValue) throws ParseException {
|
||||
Map<String, Integer> ordered = new HashMap<>();
|
||||
String[] orderedArray = splitOnNulls(preferenceValue);
|
||||
for (String entry : orderedArray) {
|
||||
String[] idRank = entry.split(":"); //$NON-NLS-1$
|
||||
if (idRank.length != 2) {
|
||||
throw new ParseException(entry, 0);
|
||||
}
|
||||
String id = idRank[0];
|
||||
int rank;
|
||||
try {
|
||||
rank = Integer.parseInt(idRank[1]);
|
||||
} catch (NumberFormatException e) {
|
||||
throw new ParseException(entry, 0);
|
||||
}
|
||||
ordered.put(id, Integer.valueOf(rank));
|
||||
}
|
||||
return ordered;
|
||||
}
|
||||
|
||||
/**
|
||||
* See Bug 558809. Oomph seems to have failed at times to encode/decode nul character '\0'
|
||||
* from the format it is stored in Oomph setup files. We can have ${0x0} instead of \0, infact
|
||||
* there can be multiple $, so $$$$${0x0} is a valid split too.
|
||||
*/
|
||||
private static String[] splitOnNulls(String preference) {
|
||||
return preference.split("\\000|(\\$+\\{0x0\\})"); //$NON-NLS-1$
|
||||
}
|
||||
}
|
|
@ -14,6 +14,7 @@
|
|||
*******************************************************************************/
|
||||
package org.eclipse.cdt.internal.ui.text.contentassist;
|
||||
|
||||
import java.text.ParseException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
|
@ -24,7 +25,6 @@ import java.util.Iterator;
|
|||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.StringTokenizer;
|
||||
|
||||
import org.eclipse.cdt.internal.ui.util.Messages;
|
||||
import org.eclipse.cdt.ui.CUIPlugin;
|
||||
|
@ -272,19 +272,19 @@ public final class CompletionProposalComputerRegistry {
|
|||
|
||||
private List<CompletionProposalCategory> getCategories(List<IConfigurationElement> elements) {
|
||||
IPreferenceStore store = CUIPlugin.getDefault().getPreferenceStore();
|
||||
String preference = store.getString(PreferenceConstants.CODEASSIST_EXCLUDED_CATEGORIES);
|
||||
Set<String> disabled = new HashSet<>();
|
||||
StringTokenizer tok = new StringTokenizer(preference, "\0"); //$NON-NLS-1$
|
||||
while (tok.hasMoreTokens())
|
||||
disabled.add(tok.nextToken());
|
||||
Map<String, Integer> ordered = new HashMap<>();
|
||||
preference = store.getString(PreferenceConstants.CODEASSIST_CATEGORY_ORDER);
|
||||
tok = new StringTokenizer(preference, "\0"); //$NON-NLS-1$
|
||||
while (tok.hasMoreTokens()) {
|
||||
StringTokenizer inner = new StringTokenizer(tok.nextToken(), ":"); //$NON-NLS-1$
|
||||
String id = inner.nextToken();
|
||||
int rank = Integer.parseInt(inner.nextToken());
|
||||
ordered.put(id, Integer.valueOf(rank));
|
||||
Set<String> disabled = Collections.emptySet();
|
||||
Map<String, Integer> ordered = Collections.emptyMap();
|
||||
boolean parseFailed = false;
|
||||
try {
|
||||
disabled = CompletionProposalComputerPreferenceParser
|
||||
.parseExcludedCategories(store.getString(PreferenceConstants.CODEASSIST_EXCLUDED_CATEGORIES));
|
||||
ordered = CompletionProposalComputerPreferenceParser
|
||||
.parseCategoryOrder(store.getString(PreferenceConstants.CODEASSIST_CATEGORY_ORDER));
|
||||
} catch (ParseException e) {
|
||||
// Failed to parse user setting, clear all settings
|
||||
// and display error message to user allowing them to
|
||||
// reset on first use
|
||||
parseFailed = true;
|
||||
}
|
||||
|
||||
List<CompletionProposalCategory> categories = new ArrayList<>();
|
||||
|
@ -296,13 +296,20 @@ public final class CompletionProposalComputerRegistry {
|
|||
|
||||
CompletionProposalCategory category = new CompletionProposalCategory(element, this);
|
||||
categories.add(category);
|
||||
category.setIncluded(!disabled.contains(category.getId()));
|
||||
Integer rank = ordered.get(category.getId());
|
||||
if (rank != null) {
|
||||
int r = rank.intValue();
|
||||
boolean separate = r < 0xffff;
|
||||
category.setSeparateCommand(separate);
|
||||
category.setSortOrder(r);
|
||||
if (parseFailed) {
|
||||
// When parse has failed we do the same thing as if the user had disabled
|
||||
// off ever proposal category. This causes a pop-up on first completion
|
||||
// attempt with the option of resetting defaults
|
||||
category.setIncluded(false);
|
||||
} else {
|
||||
category.setIncluded(!disabled.contains(category.getId()));
|
||||
Integer rank = ordered.get(category.getId());
|
||||
if (rank != null) {
|
||||
int r = rank.intValue();
|
||||
boolean separate = r < 0xffff;
|
||||
category.setSeparateCommand(separate);
|
||||
category.setSortOrder(r);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (CoreException x) {
|
||||
|
|
Loading…
Add table
Reference in a new issue