diff --git a/doc/assets/xml_spec.md b/doc/assets/xml_spec.md
index fe3ca41020e..142ef548c4e 100644
--- a/doc/assets/xml_spec.md
+++ b/doc/assets/xml_spec.md
@@ -389,10 +389,18 @@ Function Return (element name: `function_return`)
```
-All Other Steps (element name: `location-only`)
+All Other Steps (element name: `location-only` and `loop-head`)
+
+A step that indicates where in the source program we are.
+
+A `location-only` step is emitted if the source location exists and
+differs from the previous one.
+
+A `loop-head` step is emitted if the location relates to the start of a loop,
+even if the previous step is also the same start of the loop (to ensure
+that it is printed out for each loop iteration)
+
-Only included if the source location exists and differs from the
-previous one.\
**Attributes**:
- `hidden`: boolean attribute
@@ -411,6 +419,9 @@ previous one.\
+
+
+
```
**XSD**:
@@ -424,6 +435,15 @@ previous one.\
+
+
+
+
+
+
+
+
+
```
Full Trace XSD
@@ -440,6 +460,7 @@ Full Trace XSD
+
diff --git a/doc/assets/xml_spec.tex b/doc/assets/xml_spec.tex
index ea78a67e99c..015b8e53dd3 100644
--- a/doc/assets/xml_spec.tex
+++ b/doc/assets/xml_spec.tex
@@ -373,10 +373,18 @@ \subsection{Trace Steps in XML}
\begin{center}
- {\Large All Other Steps} (element name: \texttt{location-only})
+ {\Large All Other Steps} (element name: \texttt{location-only} and
+\texttt{loop-head})
\end{center}
-\noindent Only included if the source location exists and differs from the previous one.\\
+\noindent A step that indicates where in the source program we are.
+
+A \texttt{location-only} step is emitted if the source location exists and
+differs from the previous one.
+
+A \texttt{loop-head} step is emitted if the location relates to the start of a loop,
+even if the previous step is also the same start of the loop (to ensure
+that it is printed out for each loop iteration) \\
\noindent\textbf{Attributes}:
\begin{itemize}
@@ -395,6 +403,9 @@ \subsection{Trace Steps in XML}
+
+
+
\end{minted}
\noindent\textbf{XSD}:
@@ -407,6 +418,14 @@ \subsection{Trace Steps in XML}
+
+
+
+
+
+
+
+
\end{minted}
\subsection{Full Trace XSD}
@@ -422,6 +441,7 @@ \subsection{Full Trace XSD}
+
diff --git a/doc/assets/xml_spec.xsd b/doc/assets/xml_spec.xsd
index dd3c4b7f11b..40f7c078ea4 100644
--- a/doc/assets/xml_spec.xsd
+++ b/doc/assets/xml_spec.xsd
@@ -138,6 +138,15 @@
+
+
+
+
+
+
+
+
+
@@ -148,6 +157,7 @@
+
diff --git a/regression/cbmc/loophead-trace/test-json.desc b/regression/cbmc/loophead-trace/test-json.desc
new file mode 100644
index 00000000000..9754b659021
--- /dev/null
+++ b/regression/cbmc/loophead-trace/test-json.desc
@@ -0,0 +1,37 @@
+CORE
+test.c
+--unwind 6 test.c --trace --json-ui --partial-loops --slice-formula
+activate-multi-line-match
+^EXIT=10$
+^SIGNAL=0$
+\s*\{\n\s* .*\n\s* "sourceLocation": \{\n\s* .*,\n\s* .*,\n\s* "line": "5",\n\s* .*\n\s* \},\n\s* "stepType": "loop-head",\n\s* .*\n\s*\},\n\s*\{\n\s* .*\n\s* "sourceLocation": \{\n\s* .*,\n\s* .*,\n\s* "line": "5",\n\s* .*\n\s* \},\n\s* "stepType": "loop-head",\n\s* .*\n\s*\},
+--
+--
+Ensure even with sliced formulas, we get a location only step for
+each iteration of the loop (called loop-heads) when using partial loops.
+
+This test is checking the following json:(deleting the new lines after each \n to obtain
+monster regex above).
+
+\s*\{\n
+\s* .*\n
+\s* "sourceLocation": \{\n
+\s* .*,\n
+\s* .*,\n
+\s* "line": "5",\n
+\s* .*\n
+\s* \},\n
+\s* "stepType": "loop-head",\n
+\s* .*\n
+\s*\},\n
+\s*\{\n
+\s* .*\n
+\s* "sourceLocation": \{\n
+\s* .*,\n
+\s* .*,\n
+\s* "line": "5",\n
+\s* .*\n
+\s* \},\n
+\s* "stepType": "loop-head",\n
+\s* .*\n
+\s*\},
diff --git a/regression/cbmc/loophead-trace/test-xml.desc b/regression/cbmc/loophead-trace/test-xml.desc
new file mode 100644
index 00000000000..b039701d87d
--- /dev/null
+++ b/regression/cbmc/loophead-trace/test-xml.desc
@@ -0,0 +1,22 @@
+CORE
+test.c
+--unwind 6 test.c --trace --xml-ui --partial-loops --slice-formula
+activate-multi-line-match
+^EXIT=10$
+^SIGNAL=0$
+\s*\n\s* \n\s*\n\s*\n\s* \n\s*\n
+--
+--
+Ensure even with sliced formulas, we get a location only step for
+each iteration of the loop (called loop-heads) when using partial loops.
+
+This test is checking the following XML:(deleting the new lines
+after each \n to obtain the monster regex above).
+
+\s*
+\s*
+\s*
+\s*
+\s*
+\s*
+
diff --git a/regression/cbmc/loophead-trace/test.c b/regression/cbmc/loophead-trace/test.c
new file mode 100644
index 00000000000..906178e30c0
--- /dev/null
+++ b/regression/cbmc/loophead-trace/test.c
@@ -0,0 +1,12 @@
+void main(void)
+{
+ int i = 0;
+
+ while(1)
+ {
+ i = 1;
+ }
+
+ // invalid assertion
+ assert(i == 0);
+}
diff --git a/src/goto-programs/Makefile b/src/goto-programs/Makefile
index 429bbc046c3..ac22aa41a86 100644
--- a/src/goto-programs/Makefile
+++ b/src/goto-programs/Makefile
@@ -64,6 +64,7 @@ SRC = adjust_float_expressions.cpp \
slice_global_inits.cpp \
string_abstraction.cpp \
string_instrumentation.cpp \
+ structured_trace_util.cpp \
system_library_symbols.cpp \
validate_goto_model.cpp \
vcd_goto_trace.cpp \
diff --git a/src/goto-programs/json_goto_trace.cpp b/src/goto-programs/json_goto_trace.cpp
index ca9267f84b5..2bfb04ee9d7 100644
--- a/src/goto-programs/json_goto_trace.cpp
+++ b/src/goto-programs/json_goto_trace.cpp
@@ -276,22 +276,15 @@ void convert_return(
json_call_return["sourceLocation"] = location;
}
-/// Convert all other types of steps not already handled
-/// by the other conversion functions.
-/// \param [out] json_location_only: The JSON object that
-/// will contain the information about the step
-/// after this function has run.
-/// \param conversion_dependencies: A structure
-/// that contains information the conversion function
-/// needs.
void convert_default(
json_objectt &json_location_only,
- const conversion_dependenciest &conversion_dependencies)
+ const conversion_dependenciest &conversion_dependencies,
+ const default_step_kindt &step_kind)
{
const goto_trace_stept &step = conversion_dependencies.step;
const jsont &location = conversion_dependencies.location;
- json_location_only["stepType"] = json_stringt("location-only");
+ json_location_only["stepType"] = json_stringt(default_step_name(step_kind));
json_location_only["hidden"] = jsont::json_boolean(step.hidden);
json_location_only["thread"] = json_numbert(std::to_string(step.thread_nr));
json_location_only["sourceLocation"] = location;
diff --git a/src/goto-programs/json_goto_trace.h b/src/goto-programs/json_goto_trace.h
index 3bdc67c6f77..9c98fac09e7 100644
--- a/src/goto-programs/json_goto_trace.h
+++ b/src/goto-programs/json_goto_trace.h
@@ -15,7 +15,9 @@ Date: November 2005
#define CPROVER_GOTO_PROGRAMS_JSON_GOTO_TRACE_H
#include "goto_trace.h"
+#include "structured_trace_util.h"
+#include
#include
#include
#include
@@ -97,9 +99,12 @@ void convert_return(
/// \param conversion_dependencies: A structure
/// that contains information the conversion function
/// needs.
+/// \param step_kind: The kind of default step we are printing.
+/// See \ref default_step_kind
void convert_default(
json_objectt &json_location_only,
- const conversion_dependenciest &conversion_dependencies);
+ const conversion_dependenciest &conversion_dependencies,
+ const default_step_kindt &step_kind);
/// Templated version of the conversion method.
/// Works by dispatching to the more specialised
@@ -186,10 +191,14 @@ void convert(
case goto_trace_stept::typet::SHARED_WRITE:
case goto_trace_stept::typet::CONSTRAINT:
case goto_trace_stept::typet::NONE:
- if(source_location != previous_source_location)
+ const auto default_step_kind = ::default_step_kind(*step.pc);
+ if(
+ source_location != previous_source_location ||
+ default_step_kind == default_step_kindt::LOOP_HEAD)
{
json_objectt &json_location_only = dest_array.push_back().make_object();
- convert_default(json_location_only, conversion_dependencies);
+ convert_default(
+ json_location_only, conversion_dependencies, default_step_kind);
}
}
diff --git a/src/goto-programs/structured_trace_util.cpp b/src/goto-programs/structured_trace_util.cpp
new file mode 100644
index 00000000000..7aecf8726c4
--- /dev/null
+++ b/src/goto-programs/structured_trace_util.cpp
@@ -0,0 +1,35 @@
+/*******************************************************************\
+
+Author: Diffblue
+
+\*******************************************************************/
+
+/// \file
+/// Utilities for printing location info steps in the trace in a format
+/// agnostic way
+
+#include "structured_trace_util.h"
+#include
+
+default_step_kindt
+default_step_kind(const goto_programt::instructiont &instruction)
+{
+ const bool is_loophead = std::any_of(
+ instruction.incoming_edges.begin(),
+ instruction.incoming_edges.end(),
+ [](goto_programt::targett t) { return t->is_backwards_goto(); });
+
+ return is_loophead ? default_step_kindt::LOOP_HEAD
+ : default_step_kindt::LOCATION_ONLY;
+}
+std::string default_step_name(const default_step_kindt &step_type)
+{
+ switch(step_type)
+ {
+ case default_step_kindt::LOCATION_ONLY:
+ return "location-only";
+ case default_step_kindt::LOOP_HEAD:
+ return "loop-head";
+ }
+ UNREACHABLE;
+}
diff --git a/src/goto-programs/structured_trace_util.h b/src/goto-programs/structured_trace_util.h
new file mode 100644
index 00000000000..67dd1d5395a
--- /dev/null
+++ b/src/goto-programs/structured_trace_util.h
@@ -0,0 +1,39 @@
+/*******************************************************************\
+
+Author: Diffblue
+
+\*******************************************************************/
+
+/// \file
+/// Utilities for printing location info steps in the trace in a format
+/// agnostic way
+
+#ifndef CPROVER_GOTO_PROGRAMS_STRUCTURED_TRACE_UTIL_H
+#define CPROVER_GOTO_PROGRAMS_STRUCTURED_TRACE_UTIL_H
+
+#include "goto_program.h"
+#include
+
+/// There are two kinds of step for location markers - location-only and
+/// loop-head (for locations associated with the first step of a loop).
+enum class default_step_kindt
+{
+ LOCATION_ONLY,
+ LOOP_HEAD
+};
+
+/// Identify for a given instruction whether it is a loophead or just a location
+///
+/// Loopheads are determined by whether there is backwards jump to them. This
+/// matches the loop detection used for loop IDs
+/// \param instruction: The instruction to inspect.
+/// \return LOOP_HEAD if this is a loop head, otherwise LOCATION_ONLY
+default_step_kindt
+default_step_kind(const goto_programt::instructiont &instruction);
+
+/// Turns a \ref default_step_kindt into a string that can be used in the trace
+/// \param step_type: The kind of step, deduced from \ref default_step_kind
+/// \return Either "loop-head" or "location-only"
+std::string default_step_name(const default_step_kindt &step_type);
+
+#endif // CPROVER_GOTO_PROGRAMS_STRUCTURED_TRACE_UTIL_H
diff --git a/src/goto-programs/xml_goto_trace.cpp b/src/goto-programs/xml_goto_trace.cpp
index caf41b1c83d..964545c26be 100644
--- a/src/goto-programs/xml_goto_trace.cpp
+++ b/src/goto-programs/xml_goto_trace.cpp
@@ -22,6 +22,7 @@ Author: Daniel Kroening
#include
#include "printf_formatter.h"
+#include "structured_trace_util.h"
#include "xml_expr.h"
bool full_lhs_value_includes_binary(
@@ -237,12 +238,22 @@ void convert(
case goto_trace_stept::typet::GOTO:
case goto_trace_stept::typet::ASSUME:
case goto_trace_stept::typet::NONE:
- if(source_location!=previous_source_location)
+ {
+ // If this is just a source location then we output only the first
+ // location of a sequence of same locations.
+ // However, we don't want to suppress loop head locations because
+ // they might come from different loop iterations. If we suppressed
+ // them it would be impossible to know in which loop iteration
+ // we are in.
+ const auto default_step_kind = ::default_step_kind(*step.pc);
+ if(
+ source_location != previous_source_location ||
+ default_step_kind == default_step_kindt::LOOP_HEAD)
{
- // just the source location
if(!xml_location.name.empty())
{
- xmlt &xml_location_only=dest.new_element("location-only");
+ xmlt &xml_location_only =
+ dest.new_element(default_step_name(default_step_kind));
xml_location_only.set_attribute_bool("hidden", step.hidden);
xml_location_only.set_attribute(
@@ -253,6 +264,8 @@ void convert(
xml_location_only.new_element().swap(xml_location);
}
}
+ break;
+ }
}
if(source_location.is_not_nil() && !source_location.get_file().empty())