From 023a985bfc8a627b1e4ccff797fbc33e3d064d6c Mon Sep 17 00:00:00 2001
From: Kevin O'Connor <kevin@koconnor.net>
Date: Thu, 14 Jan 2021 22:13:50 -0500
Subject: [PATCH] gcode_macro: Use deepcopy() on get_status() results

If a get_status() method returns a mutable object (such as a list or
dict) then it would be possible for a gcode command template to
incorrectly alter the program's internal state.  Perform a deepcopy()
operation on all get_status() return results to avoid that.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
---
 klippy/extras/gcode_macro.py    | 9 ++++-----
 klippy/extras/heaters.py        | 4 ++--
 klippy/extras/query_endstops.py | 2 +-
 klippy/extras/save_variables.py | 2 +-
 4 files changed, 8 insertions(+), 9 deletions(-)

diff --git a/klippy/extras/gcode_macro.py b/klippy/extras/gcode_macro.py
index aae5f3bbb..3121024e4 100644
--- a/klippy/extras/gcode_macro.py
+++ b/klippy/extras/gcode_macro.py
@@ -1,9 +1,9 @@
 # Add ability to define custom g-code macros
 #
-# Copyright (C) 2018-2019  Kevin O'Connor <kevin@koconnor.net>
+# Copyright (C) 2018-2021  Kevin O'Connor <kevin@koconnor.net>
 #
 # This file may be distributed under the terms of the GNU GPLv3 license.
-import traceback, logging, ast
+import traceback, logging, ast, copy
 import jinja2
 
 
@@ -26,7 +26,7 @@ class GetStatusWrapper:
             raise KeyError(val)
         if self.eventtime is None:
             self.eventtime = self.printer.get_reactor().monotonic()
-        self.cache[sval] = res = dict(po.get_status(self.eventtime))
+        self.cache[sval] = res = copy.deepcopy(po.get_status(self.eventtime))
         return res
     def __contains__(self, val):
         try:
@@ -157,9 +157,8 @@ class GCodeMacro:
         pdesc = "Renamed builtin of '%s'" % (self.alias,)
         self.gcode.register_command(self.rename_existing, prev_cmd, desc=pdesc)
         self.gcode.register_command(self.alias, self.cmd, desc=self.cmd_desc)
-        return dict(self.variables)
     def get_status(self, eventtime):
-        return dict(self.variables)
+        return self.variables
     cmd_SET_GCODE_VARIABLE_help = "Set the value of a G-Code macro variable"
     def cmd_SET_GCODE_VARIABLE(self, gcmd):
         variable = gcmd.get('VARIABLE')
diff --git a/klippy/extras/heaters.py b/klippy/extras/heaters.py
index 829e9759a..92d56db9d 100644
--- a/klippy/extras/heaters.py
+++ b/klippy/extras/heaters.py
@@ -284,8 +284,8 @@ class PrinterHeaters:
                 "G-Code sensor id %s already registered" % (gcode_id,))
         self.gcode_id_to_sensor[gcode_id] = psensor
     def get_status(self, eventtime):
-        return {'available_heaters': list(self.available_heaters),
-                'available_sensors': list(self.available_sensors)}
+        return {'available_heaters': self.available_heaters,
+                'available_sensors': self.available_sensors}
     def turn_off_all_heaters(self, print_time=0.):
         for heater in self.heaters.values():
             heater.set_temp(0.)
diff --git a/klippy/extras/query_endstops.py b/klippy/extras/query_endstops.py
index 0bbb11c2e..949e15858 100644
--- a/klippy/extras/query_endstops.py
+++ b/klippy/extras/query_endstops.py
@@ -8,7 +8,7 @@ class QueryEndstops:
     def __init__(self, config):
         self.printer = config.get_printer()
         self.endstops = []
-        self.last_state = {}
+        self.last_state = []
         # Register webhook if server is available
         webhooks = self.printer.lookup_object('webhooks')
         webhooks.register_endpoint(
diff --git a/klippy/extras/save_variables.py b/klippy/extras/save_variables.py
index 6c88e3cf1..f15dbc11a 100644
--- a/klippy/extras/save_variables.py
+++ b/klippy/extras/save_variables.py
@@ -57,7 +57,7 @@ class SaveVariables:
         gcmd.respond_info("Variable Saved")
         self.loadVariables()
     def get_status(self, eventtime):
-        return {'variables': dict(self.allVariables)}
+        return {'variables': self.allVariables}
 
 def load_config(config):
     return SaveVariables(config)