diff --git a/lib/core/profiling.py b/lib/core/profiling.py index a5936beadfe..6cddfff3bc8 100644 --- a/lib/core/profiling.py +++ b/lib/core/profiling.py @@ -23,7 +23,9 @@ def profile(profileOutputFile=None): os.remove(profileOutputFile) # Start sqlmap main function and generate a raw profile file - cProfile.run("start()", profileOutputFile) + # Note: run() would exec inside __main__, which on pip installs is the console script, not sqlmap.py + from lib.controller.controller import start + cProfile.runctx("start()", {"start": start}, {}, profileOutputFile) infoMsg = "execution profiled and stored into file '%s' (e.g. 'gprof2dot -f pstats %s | dot -Tpng -o /tmp/sqlmap_profile.png')" % (profileOutputFile, profileOutputFile) logger.info(infoMsg) diff --git a/lib/core/settings.py b/lib/core/settings.py index 707bebc631c..8589e1fe485 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.10.8.22" +VERSION = "1.10.8.23" TYPE = "dev" if VERSION.count('.') > 2 and VERSION.split('.')[-1] != '0' else "stable" TYPE_COLORS = {"dev": 33, "stable": 90, "pip": 34} VERSION_STRING = "sqlmap/%s#%s" % ('.'.join(VERSION.split('.')[:-1]) if VERSION.count('.') > 2 and VERSION.split('.')[-1] == '0' else VERSION, TYPE) diff --git a/sqlmap.py b/sqlmap.py index 7728169a59e..cc0f139d859 100755 --- a/sqlmap.py +++ b/sqlmap.py @@ -221,7 +221,6 @@ def main(): from lib.controller.controller import start if conf.profile: from lib.core.profiling import profile - globals()["start"] = start profile() else: try: diff --git a/tests/test_profiling.py b/tests/test_profiling.py new file mode 100644 index 00000000000..e1f6c098be9 --- /dev/null +++ b/tests/test_profiling.py @@ -0,0 +1,59 @@ +#!/usr/bin/env python + +""" +Copyright (c) 2006-2026 sqlmap developers (https://sqlmap.org) +See the file 'LICENSE' for copying permission + +Switch '--profile' (lib/core/profiling.py). The profiled statement must resolve +'start' from an explicit namespace: on pip installs __main__ is the console +script, not sqlmap.py, so relying on it raised NameError (#6096). This test runs +under a unittest __main__ that has no 'start' either, so it covers that case. +""" + +import os +import pstats +import shutil +import sys +import tempfile +import unittest + +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) +from _testutils import bootstrap +bootstrap() + +import lib.controller.controller as controller +from lib.core.profiling import profile + + +class TestProfiling(unittest.TestCase): + def setUp(self): + self._saved = controller.start + self._dir = tempfile.mkdtemp() + self._out = os.path.join(self._dir, "sqlmap_profile.raw") + + def tearDown(self): + controller.start = self._saved + shutil.rmtree(self._dir, ignore_errors=True) + + def test_profile_does_not_need_start_in_main(self): + calls = [] + controller.start = lambda *args, **kwargs: calls.append(True) + + profile(self._out) + + self.assertEqual(calls, [True], msg="start() was never run under the profiler") + self.assertTrue(os.path.exists(self._out), msg="no raw profile written") + self.assertTrue(pstats.Stats(self._out).stats, msg="raw profile holds no stats") + + def test_profile_overwrites_a_stale_output_file(self): + controller.start = lambda *args, **kwargs: None + + with open(self._out, "wb") as f: + f.write(b"not a pstats file") + + profile(self._out) + self.assertTrue(pstats.Stats(self._out).stats) + + +if __name__ == "__main__": + unittest.main()