Skip to content

Commit

Permalink
NullPointerException when last state of the trace is *not* in
Browse files Browse the repository at this point in the history
the model.

If the final state of a counterexample is excluded from the model
because state- or action-constraints evaluate to false, it is not
written to the trace file.  This omission causes a NullPointerException
when attempting to read the final state from the trace file during
counterexample reconstruction.  To address this issue, the final state
is now explicitly appended to the counterexample to ensure completeness
and avoid runtime errors.

[Bug][TLC]

Signed-off-by: Markus Alexander Kuppe <[email protected]>
  • Loading branch information
lemmy committed Nov 22, 2024
1 parent 94a4bae commit 36bea60
Show file tree
Hide file tree
Showing 15 changed files with 186 additions and 9 deletions.
4 changes: 4 additions & 0 deletions tlatools/org.lamport.tlatools/src/tlc2/tool/ModelChecker.java
Original file line number Diff line number Diff line change
Expand Up @@ -1072,6 +1072,10 @@ public void resume() {
}
}

/**
* PostCondition:
* IF IsInitial(s) THEN s \in Range(Trace) ELSE s \notin Range(Trace)
*/
@Override
public TLCStateInfo[] getTraceInfo(TLCState s) throws IOException {
return trace.getTrace(s);
Expand Down
13 changes: 13 additions & 0 deletions tlatools/org.lamport.tlatools/src/tlc2/tool/Worker.java
Original file line number Diff line number Diff line change
Expand Up @@ -639,6 +639,19 @@ private final void doPostCondition(TLCState curState, TLCState succState) throws
} else if (succState == null) {
trace.addAll(Arrays.asList(tlc.getTraceInfo(curState)));
trace.add(tool.getState(curState, trace.getLast().state));
} else if (succState.allAssigned() && succState.workerId == Short.MAX_VALUE) {
if (curState.isInitial()) {
// Handle case where successor is fully assigned but not in the model because
// of an action- or state-constraint.
trace.add(tool.getState(curState.fingerPrint()));
trace.add(tool.getState(succState, curState));
} else {
// succState is not in the model because of an action- or state-constraint.
// Therefore, calling tlc.getTraceInfo(succState) would throw a NPE.
trace.addAll(Arrays.asList(tlc.getTraceInfo(curState)));
trace.add(tool.getState(curState, trace.getLast().state));
trace.add(tool.getState(succState, curState));
}
} else {
trace.addAll(Arrays.asList(tlc.getTraceInfo(succState)));
trace.add(tool.getState(succState, curState));
Expand Down
3 changes: 2 additions & 1 deletion tlatools/org.lamport.tlatools/test-model/DotConstrained.cfg
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
INIT Init
NEXT Next
ACTION_CONSTRAINT ActionConstraint
INVARIANT Inv
INVARIANT Inv
POSTCONDITION PostCondition
5 changes: 5 additions & 0 deletions tlatools/org.lamport.tlatools/test-model/Github1087.cfg
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
INIT Init2
NEXT Next
ACTION_CONSTRAINT ActionConstraint
INVARIANT Inv
POSTCONDITION PostCondition2
12 changes: 11 additions & 1 deletion tlatools/org.lamport.tlatools/test-model/Github602.tla
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
------ MODULE Github602 ------
EXTENDS Integers
EXTENDS Integers, TLC, TLCExt

VARIABLE x

Expand All @@ -17,6 +17,16 @@ Constraint == x \in Nat
Inv == x >= 0

ActionConstraint == x' \in Nat

PostCondition ==
/\ TLCSet(42, TLCGet("generated"))
/\ ToTrace(CounterExample) = << [x |-> 0],[x |-> 1],[x |-> -1] >>

Init2 == x = 1

PostCondition2 ==
/\ TLCSet(42, TLCGet("generated"))
/\ ToTrace(CounterExample) = << [x |-> 1],[x |-> -1] >>
====

---- CONFIG Github602 ----
Expand Down
3 changes: 2 additions & 1 deletion tlatools/org.lamport.tlatools/test-model/TLCGetAll.tla
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,8 @@ Sum(seq) ==
IN S[Len(seq)]

PostCondition ==
LET vals == TLCGet("all")
/\ TLCSet(42, TLCGet("generated"))
/\ LET vals == TLCGet("all")
IN /\ DOMAIN vals = {R, 42}
\* vars[R] is a tuple with length N where N is the number of workers.
\* In other words, vars[R] has the value of TLCGet(R) for each worker.
Expand Down
3 changes: 2 additions & 1 deletion tlatools/org.lamport.tlatools/test-model/ViewMap.tla
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,8 @@ view == <<buffer, waitset>>
CONSTANTS c1, c2, p1

PostCondition ==
CounterExample =
/\ TLCSet(42, TLCGet("generated"))
/\ CounterExample =
[ state |->
{ << 1,
[ buffer |-> <<>>,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ Spec ==
Init /\ [][Next]_x

PostCondition ==
TLCGet(0) = 63
/\ TLCSet(42, TLCGet("generated"))
/\ TLCGet(0) = 63

=======
15 changes: 15 additions & 0 deletions tlatools/org.lamport.tlatools/test/tlc2/tool/ChainedCdotsTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,16 +25,21 @@
******************************************************************************/
package tlc2.tool;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;

import java.io.IOException;
import java.util.List;

import org.junit.Test;

import tlc2.TLCGlobals;
import tlc2.output.EC;
import tlc2.tool.impl.Tool;
import tlc2.tool.liveness.ModelCheckerTestCase;
import tlc2.value.IValue;
import tlc2.value.impl.IntValue;

public class ChainedCdotsTest extends ModelCheckerTestCase {

Expand All @@ -58,5 +63,15 @@ public void testSpec() throws IOException {

assertTrue(recorder.recordedWithStringValues(EC.TLC_STATS, "9", "4", "0"));
assertTrue(recorder.recordedWithStringValue(EC.TLC_SEARCH_DEPTH, "2"));

// Assert POSTCONDITION.
assertFalse(recorder.recorded(EC.TLC_ASSUMPTION_FALSE));
assertFalse(recorder.recorded(EC.TLC_ASSUMPTION_EVALUATION_ERROR));

// Check that POSTCONDITION wrote the number of generated states to a TLCSet
// register.
final List<IValue> allValue = TLCGlobals.mainChecker.getAllValue(42);
assertTrue(!allValue.isEmpty());
assertEquals(IntValue.gen(9), allValue.get(0));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@

package tlc2.tool;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
Expand All @@ -38,11 +39,14 @@
import org.junit.Test;

import tla2sany.semantic.SemanticNode;
import tlc2.TLCGlobals;
import tlc2.output.EC;
import tlc2.output.EC.ExitStatus;
import tlc2.tool.liveness.ModelCheckerTestCase;
import tlc2.util.DotStateWriter;
import tlc2.util.IStateWriter;
import tlc2.value.IValue;
import tlc2.value.impl.IntValue;
import util.FileUtil;

public class DotConstrainedTest extends ModelCheckerTestCase {
Expand Down Expand Up @@ -101,6 +105,16 @@ public void testSpec() {

assertTrue(constrained.get());

// Check that POSTCONDITION wrote the number of generated states to a TLCSet
// register.
final List<IValue> allValue = TLCGlobals.mainChecker.getAllValue(42);
assertTrue(!allValue.isEmpty());
assertEquals(IntValue.gen(4), allValue.get(0));

// Assert POSTCONDITION.
assertFalse(recorder.recorded(EC.TLC_ASSUMPTION_FALSE));
assertFalse(recorder.recorded(EC.TLC_ASSUMPTION_EVALUATION_ERROR));

assertZeroUncovered();
}
}
77 changes: 77 additions & 0 deletions tlatools/org.lamport.tlatools/test/tlc2/tool/Github1087Test.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
/*******************************************************************************
* Copyright (c) 2024 Microsoft Corp. All rights reserved.
*
* The MIT License (MIT)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
* of the Software, and to permit persons to whom the Software is furnished to do
* so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*
* Contributors:
* Markus Alexander Kuppe - initial API and implementation
******************************************************************************/
package tlc2.tool;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;

import java.util.ArrayList;
import java.util.List;

import org.junit.Test;

import tlc2.TLCGlobals;
import tlc2.output.EC;
import tlc2.output.EC.ExitStatus;
import tlc2.tool.liveness.ModelCheckerTestCase;
import tlc2.value.IValue;
import tlc2.value.impl.IntValue;

public class Github1087Test extends ModelCheckerTestCase {

public Github1087Test() {
super("Github602", new String[] { "-config", "Github1087.cfg" }, ExitStatus.VIOLATION_SAFETY);
}

@Test
public void testSpec() {
assertTrue(recorder.recorded(EC.TLC_FINISHED));
assertFalse(recorder.recorded(EC.GENERAL));

assertTrue(recorder.recordedWithStringValues(EC.TLC_STATS, "2", "1", "0"));

// Assert it has found the temporal violation and also a counter example
assertTrue(recorder.recorded(EC.TLC_INVARIANT_VIOLATED_BEHAVIOR));

// Assert the error trace
assertTrue(recorder.recorded(EC.TLC_STATE_PRINT2));
final List<String> expectedTrace = new ArrayList<String>(2);
expectedTrace.add("x = 1");
expectedTrace.add("x = -1");
assertTraceWith(recorder.getRecords(EC.TLC_STATE_PRINT2), expectedTrace);

// Check that POSTCONDITION wrote the number of generated states to a TLCSet
// register.
final List<IValue> allValue = TLCGlobals.mainChecker.getAllValue(42);
assertTrue(!allValue.isEmpty());
assertEquals(IntValue.gen(2), allValue.get(0));

// Assert POSTCONDITION.
assertFalse(recorder.recorded(EC.TLC_ASSUMPTION_FALSE));
assertFalse(recorder.recorded(EC.TLC_ASSUMPTION_EVALUATION_ERROR));
}
}
17 changes: 17 additions & 0 deletions tlatools/org.lamport.tlatools/test/tlc2/tool/TLCGetAllTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,19 @@
******************************************************************************/
package tlc2.tool;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;

import java.util.List;

import org.junit.Test;

import tlc2.TLCGlobals;
import tlc2.output.EC;
import tlc2.tool.liveness.ModelCheckerTestCase;
import tlc2.value.IValue;
import tlc2.value.impl.IntValue;

public class TLCGetAllTest extends ModelCheckerTestCase {

Expand All @@ -47,5 +54,15 @@ protected int getNumberOfThreads() {
public void testSpec() {
assertFalse(recorder.recorded(EC.GENERAL));
assertZeroUncovered();

// Assert POSTCONDITION.
assertFalse(recorder.recorded(EC.TLC_ASSUMPTION_FALSE));
assertFalse(recorder.recorded(EC.TLC_ASSUMPTION_EVALUATION_ERROR));

// Check that POSTCONDITION wrote the number of generated states to a TLCSet
// register.
final List<IValue> allValue = TLCGlobals.mainChecker.getAllValue(42);
assertTrue(!allValue.isEmpty());
assertEquals(IntValue.gen(10101), allValue.get(0));
}
}
14 changes: 14 additions & 0 deletions tlatools/org.lamport.tlatools/test/tlc2/tool/ViewMapTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
******************************************************************************/
package tlc2.tool;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;

Expand All @@ -33,9 +34,12 @@

import org.junit.Test;

import tlc2.TLCGlobals;
import tlc2.output.EC;
import tlc2.output.EC.ExitStatus;
import tlc2.tool.liveness.ModelCheckerTestCase;
import tlc2.value.IValue;
import tlc2.value.impl.IntValue;

public class ViewMapTest extends ModelCheckerTestCase {

Expand Down Expand Up @@ -76,5 +80,15 @@ public void testSpec() {
assertTraceWith(recorder.getRecords(EC.TLC_STATE_PRINT2), expectedTrace, expectedActions);

assertUncovered("line 91, col 60 to line 91, col 73 of module ViewMap: 0");

// Assert POSTCONDITION.
assertFalse(recorder.recorded(EC.TLC_ASSUMPTION_FALSE));
assertFalse(recorder.recorded(EC.TLC_ASSUMPTION_EVALUATION_ERROR));

// Check that POSTCONDITION wrote the number of generated states to a TLCSet
// register.
final List<IValue> allValue = TLCGlobals.mainChecker.getAllValue(42);
assertTrue(!allValue.isEmpty());
assertEquals(IntValue.gen(43), allValue.get(0));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,15 +26,20 @@

package tlc2.tool.liveness;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;

import java.io.File;
import java.util.List;

import org.junit.Test;

import tlc2.TLCGlobals;
import tlc2.output.EC;
import tlc2.output.EC.ExitStatus;
import tlc2.value.IValue;
import tlc2.value.impl.IntValue;

public class OneBitMutexNoSymmetryTest extends ModelCheckerTestCase {

Expand Down Expand Up @@ -67,8 +72,8 @@ public void testSpec() {

// Check that POSTCONDITION wrote the number of generated states to a TLCSet
// register.
// final List<IValue> allValue = TLCGlobals.mainChecker.getAllValue(42);
// assertTrue(!allValue.isEmpty());
// assertEquals(IntValue.gen(244), allValue.get(0));
final List<IValue> allValue = TLCGlobals.mainChecker.getAllValue(42);
assertTrue(!allValue.isEmpty());
assertEquals(IntValue.gen(244), allValue.get(0));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@

import org.junit.Test;

import tlc2.TLC;
import tlc2.output.EC;
import tlc2.tool.liveness.ModelCheckerTestCase;

Expand Down

0 comments on commit 36bea60

Please sign in to comment.