Skip to content

Commit 874eb22

Browse files
committed
add methods for escaping and unescaping keywords in Strings.
see #26 for discussion.
1 parent 3c0fced commit 874eb22

File tree

4 files changed

+175
-31
lines changed

4 files changed

+175
-31
lines changed

src/org/sosy_lab/java_smt/api/FormulaManager.java

+14
Original file line numberDiff line numberDiff line change
@@ -241,4 +241,18 @@ <T extends Formula> T makeApplication(
241241
* </ul>
242242
*/
243243
boolean isValidName(String variableName);
244+
245+
/**
246+
* Get an escaped symbol/name for variables or undefined functions, if necessary.
247+
*
248+
* <p>See {@link #isValidName(String)} for further details.
249+
*/
250+
String escape(String variableName);
251+
252+
/**
253+
* Get an escaped symbol/name for variables or undefined functions, if necessary.
254+
*
255+
* <p>See {@link #isValidName(String)} for further details.
256+
*/
257+
String unescape(String variableName);
244258
}

src/org/sosy_lab/java_smt/basicimpl/AbstractFormulaManager.java

+73-4
Original file line numberDiff line numberDiff line change
@@ -24,12 +24,14 @@
2424
import com.google.common.annotations.VisibleForTesting;
2525
import com.google.common.base.CharMatcher;
2626
import com.google.common.base.Preconditions;
27+
import com.google.common.collect.ImmutableBiMap;
2728
import com.google.common.collect.ImmutableMap;
2829
import com.google.common.collect.ImmutableSet;
2930
import java.util.Arrays;
3031
import java.util.LinkedHashMap;
3132
import java.util.List;
3233
import java.util.Map;
34+
import java.util.Map.Entry;
3335
import org.checkerframework.checker.nullness.qual.Nullable;
3436
import org.sosy_lab.common.Appender;
3537
import org.sosy_lab.java_smt.api.ArrayFormulaManager;
@@ -93,6 +95,15 @@ public abstract class AbstractFormulaManager<TFormulaInfo, TType, TEnv, TFuncDec
9395
*/
9496
private static final CharMatcher DISALLOWED_CHARACTERS = CharMatcher.anyOf("|\\");
9597

98+
/** Mapping of disallowed char to their escaped counterparts. */
99+
/* Keep this map in sync with {@link #DISALLOWED_CHARACTERS}.
100+
* Counterparts can be any unique string. For optimization we could even use plain chars. */
101+
@VisibleForTesting
102+
public static final ImmutableBiMap<Character, String> DISALLOWED_CHARACTER_REPLACEMENT =
103+
ImmutableBiMap.<Character, String>builder().put('|', "pipe").put('\\', "backslash").build();
104+
105+
private static final char ESCAPE = '$'; // just some allowed symbol, can be any char
106+
96107
private final @Nullable AbstractArrayFormulaManager<TFormulaInfo, TType, TEnv, TFuncDecl>
97108
arrayManager;
98109

@@ -455,20 +466,78 @@ public static void checkVariableName(final String variableName) {
455466
Preconditions.checkArgument(
456467
!variableName.isEmpty(), "Identifier for variable should not be empty.");
457468
Preconditions.checkArgument(
458-
!AbstractFormulaManager.BASIC_OPERATORS.contains(variableName),
469+
!BASIC_OPERATORS.contains(variableName),
459470
"Identifier '%s' should not be a simple operator. %s",
460471
variableName,
461472
help);
462473
Preconditions.checkArgument(
463-
!AbstractFormulaManager.SMTLIB2_KEYWORDS.contains(variableName),
474+
!SMTLIB2_KEYWORDS.contains(variableName),
464475
"Identifier '%s' should not be a keyword of SMT-LIB2. %s",
465476
variableName,
466477
help);
467478
Preconditions.checkArgument(
468-
AbstractFormulaManager.DISALLOWED_CHARACTERS.matchesNoneOf(variableName),
479+
DISALLOWED_CHARACTERS.matchesNoneOf(variableName),
469480
"Identifier '%s' should contain an escape character %s of SMT-LIB2. %s",
470481
variableName,
471-
DISALLOWED_CHARACTERS, // toString prints UTF8-encoded escape sequence, better than nothing.
482+
DISALLOWED_CHARACTER_REPLACEMENT
483+
.keySet(), // toString prints UTF8-encoded escape sequence, better than nothing.
472484
help);
473485
}
486+
487+
/* This escaping works for simple escape sequences only, i.e., keywords are unique enough. */
488+
@Override
489+
public final String escape(String pVar) {
490+
// as long as keywords stay simple, this simple escaping is sufficient
491+
if (pVar.isEmpty() || BASIC_OPERATORS.contains(pVar) || SMTLIB2_KEYWORDS.contains(pVar)) {
492+
return ESCAPE + pVar;
493+
}
494+
if (pVar.indexOf(ESCAPE) != -1) {
495+
pVar = pVar.replace("" + ESCAPE, "" + ESCAPE + ESCAPE);
496+
}
497+
if (DISALLOWED_CHARACTERS.matchesAnyOf(pVar)) {
498+
for (Entry<Character, String> e : DISALLOWED_CHARACTER_REPLACEMENT.entrySet()) {
499+
pVar = pVar.replace(e.getKey().toString(), ESCAPE + e.getValue());
500+
}
501+
}
502+
return pVar; // unchanged
503+
}
504+
505+
/* This unescaping works for simple escape sequences only, i.e., keywords are unique enough. */
506+
@Override
507+
public final String unescape(String pVar) {
508+
int idx = pVar.indexOf(ESCAPE);
509+
if (idx != -1) {
510+
// unescape BASIC_OPERATORS and SMTLIB2_KEYWORDS
511+
if (idx == 0) {
512+
String tmp = pVar.substring(1);
513+
if (tmp.isEmpty() || BASIC_OPERATORS.contains(tmp) || SMTLIB2_KEYWORDS.contains(tmp)) {
514+
return tmp;
515+
}
516+
}
517+
518+
// unescape DISALLOWED_CHARACTERS
519+
StringBuilder str = new StringBuilder();
520+
for (int i = 0; i < pVar.length(); i++) {
521+
if (pVar.charAt(i) == ESCAPE) {
522+
if (pVar.charAt(i + 1) == ESCAPE) {
523+
str.append(ESCAPE);
524+
i += 1;
525+
} else {
526+
String rest = pVar.substring(i + 1);
527+
for (Entry<Character, String> e : DISALLOWED_CHARACTER_REPLACEMENT.entrySet()) {
528+
if (rest.startsWith(e.getValue())) {
529+
str.append(e.getKey());
530+
i += e.getValue().length();
531+
break;
532+
}
533+
}
534+
}
535+
} else {
536+
str.append(pVar.charAt(i));
537+
}
538+
}
539+
return str.toString();
540+
}
541+
return pVar; // unchanged
542+
}
474543
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
/*
2+
* JavaSMT is an API wrapper for a collection of SMT solvers.
3+
* This file is part of JavaSMT.
4+
*
5+
* Copyright (C) 2007-2018 Dirk Beyer
6+
* All rights reserved.
7+
*
8+
* Licensed under the Apache License, Version 2.0 (the "License");
9+
* you may not use this file except in compliance with the License.
10+
* You may obtain a copy of the License at
11+
*
12+
* http://www.apache.org/licenses/LICENSE-2.0
13+
*
14+
* Unless required by applicable law or agreed to in writing, software
15+
* distributed under the License is distributed on an "AS IS" BASIS,
16+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17+
* See the License for the specific language governing permissions and
18+
* limitations under the License.
19+
*/
20+
21+
package org.sosy_lab.java_smt.test;
22+
23+
import org.junit.Assert;
24+
import org.junit.Test;
25+
26+
/** inherits many tests from {@link VariableNamesTest}. */
27+
public class VariableNamesEscaperTest extends VariableNamesTest {
28+
29+
@Override
30+
boolean allowInvalidNames() {
31+
return false;
32+
}
33+
34+
@Override
35+
String getVarname() {
36+
return mgr.escape(super.getVarname());
37+
}
38+
39+
@Test
40+
public void testEscapeUnescape() {
41+
String var = super.getVarname();
42+
Assert.assertEquals(var, mgr.unescape(mgr.escape(var)));
43+
}
44+
45+
@Test
46+
public void testDoubleEscapeUnescape() {
47+
String var = getVarname();
48+
Assert.assertEquals(var, mgr.unescape(mgr.escape(var)));
49+
}
50+
}

src/org/sosy_lab/java_smt/test/VariableNamesTest.java

+38-27
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,7 @@ public static List<Object[]> getAllCombinations() {
191191
.addAll(NAMES)
192192
.addAll(AbstractFormulaManager.BASIC_OPERATORS)
193193
.addAll(AbstractFormulaManager.SMTLIB2_KEYWORDS)
194+
.addAll(AbstractFormulaManager.DISALLOWED_CHARACTER_REPLACEMENT.values())
194195
.addAll(FURTHER_SMTLIB2_KEYWORDS)
195196
.addAll(UNSUPPORTED_NAMES)
196197
.build();
@@ -209,33 +210,41 @@ protected Solvers solverToUse() {
209210
return solver;
210211
}
211212

213+
boolean allowInvalidNames() {
214+
return true;
215+
}
216+
217+
String getVarname() {
218+
return varname;
219+
}
220+
212221
@CanIgnoreReturnValue
213-
private <T extends Formula> T createVariableWith(Function<String, T> creator) {
222+
private <T extends Formula> T createVariableWith(Function<String, T> creator, String name) {
214223
final T result;
215-
if (!mgr.isValidName(varname)) {
224+
if (allowInvalidNames() && !mgr.isValidName(name)) {
216225
try {
217-
result = creator.apply(varname);
226+
result = creator.apply(name);
218227
fail();
219228
} catch (IllegalArgumentException e) {
220229
throw new AssumptionViolatedException("unsupported variable name", e);
221230
}
222231
} else {
223-
result = creator.apply(varname);
232+
result = creator.apply(name);
224233
}
225234
return result;
226235
}
227236

228237
@Test
229238
public void testBoolVariable() {
230-
createVariableWith(bmgr::makeVariable);
239+
createVariableWith(bmgr::makeVariable, getVarname());
231240
}
232241

233242
private <T extends Formula> void testName0(
234243
Function<String, T> creator, BiFunction<T, T, BooleanFormula> eq, boolean isUF)
235244
throws SolverException, InterruptedException {
236245

237246
// create a variable
238-
T var = createVariableWith(creator);
247+
T var = createVariableWith(creator, getVarname());
239248

240249
// check whether it exists with the given name
241250
Map<String, Formula> map = mgr.extractVariables(var);
@@ -244,10 +253,10 @@ private <T extends Formula> void testName0(
244253
map = mgr.extractVariablesAndUFs(var);
245254
}
246255
assertThat(map).hasSize(1);
247-
assertThat(map).containsEntry(varname, var);
256+
assertThat(map).containsEntry(getVarname(), var);
248257

249258
// check whether we can create the same variable again
250-
T var2 = createVariableWith(creator);
259+
T var2 = createVariableWith(creator, getVarname());
251260
// for simple formulas, we can expect a direct equality
252261
// (for complex formulas this is not satisfied)
253262
assertThat(var2).isEqualTo(var);
@@ -265,11 +274,12 @@ private <T extends Formula> void testName0(
265274
@SuppressWarnings("unused")
266275
String dump = mgr.dumpFormula(eq.apply(var, var)).toString();
267276

268-
// try to create a new (!) variable with a different name, the escaped previous name.
269-
varname = "|" + varname + "|";
270-
@SuppressWarnings("unused")
271-
T var3 = createVariableWith(creator);
272-
fail();
277+
if (allowInvalidNames()) {
278+
// try to create a new (!) variable with a different name, the escaped previous name.
279+
@SuppressWarnings("unused")
280+
T var3 = createVariableWith(creator, "|" + getVarname() + "|");
281+
fail();
282+
}
273283
}
274284

275285
@Test
@@ -336,7 +346,7 @@ public void testNameUF2Int() throws SolverException, InterruptedException {
336346
public void testNameExists() {
337347
requireQuantifiers();
338348

339-
IntegerFormula var = createVariableWith(imgr::makeVariable);
349+
IntegerFormula var = createVariableWith(imgr::makeVariable, getVarname());
340350
IntegerFormula zero = imgr.makeNumber(0);
341351
BooleanFormula exists = qmgr.exists(var, imgr.equal(var, zero));
342352

@@ -356,7 +366,7 @@ public Void visitQuantifier(
356366
for (Formula f : pBoundVariables) {
357367
Map<String, Formula> map = mgr.extractVariables(f);
358368
assertThat(map).hasSize(1);
359-
assertThat(map).containsEntry(varname, f);
369+
assertThat(map).containsEntry(getVarname(), f);
360370
}
361371
return null;
362372
}
@@ -370,7 +380,7 @@ protected Void visitDefault(Formula pF) {
370380

371381
@Test
372382
public void testBoolVariableNameInVisitor() {
373-
BooleanFormula var = createVariableWith(bmgr::makeVariable);
383+
BooleanFormula var = createVariableWith(bmgr::makeVariable, getVarname());
374384

375385
bmgr.visit(
376386
var,
@@ -382,65 +392,66 @@ protected Void visitDefault() {
382392

383393
@Override
384394
public Void visitAtom(BooleanFormula pAtom, FunctionDeclaration<BooleanFormula> pDecl) {
385-
assertThat(pDecl.getName()).isEqualTo(varname);
395+
assertThat(pDecl.getName()).isEqualTo(getVarname());
386396
return null;
387397
}
388398
});
389399
}
390400

391401
@Test
392402
public void testBoolVariableDump() {
393-
BooleanFormula var = createVariableWith(bmgr::makeVariable);
403+
BooleanFormula var = createVariableWith(bmgr::makeVariable, getVarname());
394404
@SuppressWarnings("unused")
395405
String dump = mgr.dumpFormula(var).toString();
396406
}
397407

398408
@Test
399409
public void testEqBoolVariableDump() {
400-
BooleanFormula var = createVariableWith(bmgr::makeVariable);
410+
BooleanFormula var = createVariableWith(bmgr::makeVariable, getVarname());
401411
@SuppressWarnings("unused")
402412
String dump = mgr.dumpFormula(bmgr.equivalence(var, var)).toString();
403413
}
404414

405415
@Test
406416
public void testIntVariable() {
407-
createVariableWith(imgr::makeVariable);
417+
createVariableWith(imgr::makeVariable, getVarname());
408418
}
409419

410420
@Test
411421
public void testInvalidRatVariable() {
412422
requireRationals();
413-
createVariableWith(rmgr::makeVariable);
423+
createVariableWith(rmgr::makeVariable, getVarname());
414424
}
415425

416426
@Test
417427
public void testBVVariable() {
418428
requireBitvectors();
419-
createVariableWith(v -> bvmgr.makeVariable(4, v));
429+
createVariableWith(v -> bvmgr.makeVariable(4, v), getVarname());
420430
}
421431

422432
@Test
423433
public void testInvalidFloatVariable() {
424434
requireFloats();
425435
createVariableWith(
426-
v -> fpmgr.makeVariable(v, FormulaType.getSinglePrecisionFloatingPointType()));
436+
v -> fpmgr.makeVariable(v, FormulaType.getSinglePrecisionFloatingPointType()),
437+
getVarname());
427438
}
428439

429440
@Test
430441
public void testArrayVariable() {
431442
requireArrays();
432-
createVariableWith(v -> amgr.makeArray(v, IntegerType, IntegerType));
443+
createVariableWith(v -> amgr.makeArray(v, IntegerType, IntegerType), getVarname());
433444
}
434445

435446
@Test
436447
public void sameBehaviorTest() {
437-
if (mgr.isValidName(varname)) {
448+
if (mgr.isValidName(getVarname())) {
438449
// should pass without exception
439-
AbstractFormulaManager.checkVariableName(varname);
450+
AbstractFormulaManager.checkVariableName(getVarname());
440451
} else {
441452
try {
442453
// should throw exception
443-
AbstractFormulaManager.checkVariableName(varname);
454+
AbstractFormulaManager.checkVariableName(getVarname());
444455
fail();
445456
} catch (IllegalArgumentException e) {
446457
}

0 commit comments

Comments
 (0)