Repository: mitmproxy/mitmproxy

Issue:
Different traceback messages appear, when try to run inappropriate file as a script
##### Steps to reproduce the problem:

1. Run mitmproxy.
2. Press `|`(`shift+\`). Input the path to not script file: 
`: script.run @focus /home/kajoj/myphoto.JPG`

Traceback: 
![image](https://user-images.githubusercontent.com/20267977/35767610-4eb01d1c-08f8-11e8-97dc-aca5225fd9a5.png)

2a. `: script.run @focus /home/kajoj/passlist.txt`

Traceback: 
![image](https://user-images.githubusercontent.com/20267977/35767631-c6d8dafe-08f8-11e8-9413-177fe69f4f51.png)

##### Any other comments? What have you tried so far?

I think these aren't only traceback messages, which may appear. 
We should take a look at this place: https://github.com/mitmproxy/mitmproxy/blob/master/mitmproxy/addons/script.py#L30


##### System information

Mitmproxy: 3.0.0.dev79 (commit 957a630)
Python: 3.5.2
OpenSSL: OpenSSL 1.1.0g 2 Nov 2017
Platform: Linux-4.4.0-112-generic-x86_64-with-Ubuntu-16.04-xenial



Discussion:
Hi, If noone else is working on it, I will take this :)


I have a question
Are we stringent that the script the user is trying to run should be a python file such as `example.py`, 
or we are giving them the flexibility to run any file until and unless it's contents are python code(which we are doing right now)? 

I would like to go with the second one
@kira0204: Are you referring to the filename extension (e.g. `.py`)? We shouldn't enforce anything in that regard.
Yes I was, thanks for the clarification :) 
Okay since we don't want the user to see all the traceback messages we can use `cut_traceback` with the parameter `_call_with_frames_removed` , I know this is not a neat solution but I am not able to find any solution apart from this :(
@kira0204 we can try to handle these exceptions here -> https://github.com/mitmproxy/mitmproxy/blob/master/mitmproxy/addons/script.py#L26
I know, but I want the user to know what is the issue with the code that he is trying to load and where. If we ignore this and go forward with the handling of the exceptions and display a generic message then the solution becomes neat but we would be disturbing the UX
@kira0204 issue about users' code exceptions is #2837 
I think the current issue happens only when `loader.exec_module` can't even parse the python file properly
@kajojify `loader.exec_module` also executes the module in it's own namespace thus there will be cases which will be user's fault because the code will contain errors. When you talked about #2837 case, `exec_module` did it's part and did not execute `request()` (because namespace), but when you invoked that on the flow it raised an exception. But if we talk about errors like syntax errors or maybe logical errors although these are user's code errors(not just exceptions) then `loader.exec_module` is responsible for it

Now, if we handle `loader.exec_module()` if it throws any error, how will we tell user where the error was?  

Resolving patch:
diff --git a/mitmproxy/addons/script.py b/mitmproxy/addons/script.py
--- a/mitmproxy/addons/script.py
+++ b/mitmproxy/addons/script.py
@@ -25,6 +25,7 @@ def load_script(path: str) -> types.ModuleType:
     sys.modules.pop(fullname, None)
     oldpath = sys.path
     sys.path.insert(0, os.path.dirname(path))
+    m = None
     try:
         loader = importlib.machinery.SourceFileLoader(fullname, path)
         spec = importlib.util.spec_from_loader(fullname, loader=loader)
@@ -32,9 +33,11 @@ def load_script(path: str) -> types.ModuleType:
         loader.exec_module(m)
         if not getattr(m, "name", None):
             m.name = path  # type: ignore
-        return m
+    except Exception as e:
+        script_error_handler(path, e, msg=str(e))
     finally:
         sys.path[:] = oldpath
+        return m
 
 
 def script_error_handler(path, exc, msg="", tb=False):
@@ -48,11 +51,11 @@ def script_error_handler(path, exc, msg="", tb=False):
     lineno = ""
     if hasattr(exc, "lineno"):
         lineno = str(exc.lineno)
-    log_msg = "in Script {}:{} {}".format(path, lineno, exception)
+    log_msg = "in script {}:{} {}".format(path, lineno, exception)
     if tb:
         etype, value, tback = sys.exc_info()
         tback = addonmanager.cut_traceback(tback, "invoke_addon")
-        log_msg = log_msg.join(["\n"] + traceback.format_exception(etype, value, tback))
+        log_msg = log_msg + "\n" + "".join(traceback.format_exception(etype, value, tback))
     ctx.log.error(log_msg)
 
 


Test patch:
diff --git a/test/mitmproxy/addons/test_script.py b/test/mitmproxy/addons/test_script.py
--- a/test/mitmproxy/addons/test_script.py
+++ b/test/mitmproxy/addons/test_script.py
@@ -12,18 +12,27 @@
 from mitmproxy.test import tutils
 
 
-def test_load_script():
-    ns = script.load_script(
-        tutils.test_data.path(
-            "mitmproxy/data/addonscripts/recorder/recorder.py"
+@pytest.mark.asyncio
+async def test_load_script():
+    with taddons.context() as tctx:
+        ns = script.load_script(
+            tutils.test_data.path(
+                "mitmproxy/data/addonscripts/recorder/recorder.py"
+            )
         )
-    )
-    assert ns.addons
+        assert ns.addons
 
-    with pytest.raises(FileNotFoundError):
         script.load_script(
             "nonexistent"
         )
+        assert await tctx.master.await_log("No such file or directory")
+
+        script.load_script(
+            tutils.test_data.path(
+                "mitmproxy/data/addonscripts/recorder/error.py"
+            )
+        )
+        assert await tctx.master.await_log("invalid syntax")
 
 
 def test_load_fullname():
diff --git a/test/mitmproxy/data/addonscripts/recorder/error.py b/test/mitmproxy/data/addonscripts/recorder/error.py
new file mode 100644
--- /dev/null
+++ b/test/mitmproxy/data/addonscripts/recorder/error.py
@@ -0,0 +1,7 @@
+"""
+This file is intended to have syntax errors for test purposes
+"""
+
+impotr recorder # Intended Syntax Error
+
+addons = [recorder.Recorder("e")]