Skip to content

Commit 1e454f1

Browse files
authored
chore(iast): fix wheel build error on macOS [backport 2.21] (#12542)
Backport 2ef9f1e from #12536 to 2.21. With this PR, all macOS wheels should include the IAST `.so` file. If not, it will trigger an error **in the CI** - Fix IAST C++ code to support macOS 10.13 or later - Enable smoke tests for all macOS builds in `build_python_3.yml` - Remove the "optional" flag for the IAST build in `setup.py` to ensure a fast failure if any issues arise APPSEC-56890 ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)
1 parent 4fc57f9 commit 1e454f1

File tree

5 files changed

+55
-25
lines changed

5 files changed

+55
-25
lines changed

ddtrace/appsec/_iast/_taint_tracking/Aspects/Helpers.cpp

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,8 @@ as_formatted_evidence(const string& text,
6060

6161
for (const auto& taint_range : text_ranges) {
6262
string content;
63-
if (!tag_mapping_mode or tag_mapping_mode.value() == TagMappingMode::Normal) {
63+
// tag_mapping_mode.value throws a compile error: 'value' is unavailable: introduced in macOS 10.13
64+
if (!tag_mapping_mode or !tag_mapping_mode.has_value() or *tag_mapping_mode == TagMappingMode::Normal) {
6465
content = get_default_content(taint_range);
6566
} else
6667
switch (*tag_mapping_mode) {
@@ -111,10 +112,11 @@ api_as_formatted_evidence(const StrType& text,
111112
}
112113

113114
TaintRangeRefs _ranges;
114-
if (!text_ranges) {
115+
if (!text_ranges or !text_ranges.has_value()) {
115116
_ranges = api_get_ranges(text);
116117
} else {
117-
_ranges = text_ranges.value();
118+
// text_ranges.value throws a compile error: 'value' is unavailable: introduced in macOS 10.13
119+
_ranges = *text_ranges;
118120
}
119121
return StrType(as_formatted_evidence(AnyTextObjectToString(text), _ranges, tag_mapping_mode, new_ranges));
120122
}
@@ -161,19 +163,22 @@ api_convert_escaped_text_to_taint_text(PyObject* taint_escaped_text,
161163

162164
switch (py_str_type) {
163165
case PyTextType::UNICODE: {
164-
const auto text_str = py::reinterpret_borrow<py::str>(text_pyobj_opt.value());
166+
// text_pyobj_opt.value throws a compile error: 'value' is unavailable: introduced in macOS 10.13
167+
const auto text_str = py::reinterpret_borrow<py::str>(*text_pyobj_opt);
165168
auto obj = api_convert_escaped_text_to_taint_text<py::str>(text_str, ranges_orig);
166169
Py_INCREF(obj.ptr());
167170
return obj.ptr();
168171
}
169172
case PyTextType::BYTES: {
170-
const auto text_bytes = py::reinterpret_borrow<py::bytes>(text_pyobj_opt.value());
173+
// text_pyobj_opt.value throws a compile error: 'value' is unavailable: introduced in macOS 10.13
174+
const auto text_bytes = py::reinterpret_borrow<py::bytes>(*text_pyobj_opt);
171175
auto obj = api_convert_escaped_text_to_taint_text<py::bytes>(text_bytes, ranges_orig);
172176
Py_INCREF(obj.ptr());
173177
return obj.ptr();
174178
}
175179
case PyTextType::BYTEARRAY: {
176-
const auto text_bytearray = py::reinterpret_borrow<py::bytearray>(text_pyobj_opt.value());
180+
// text_pyobj_opt.value throws a compile error: 'value' is unavailable: introduced in macOS 10.13
181+
const auto text_bytearray = py::reinterpret_borrow<py::bytearray>(*text_pyobj_opt);
177182
auto obj = api_convert_escaped_text_to_taint_text<py::bytearray>(text_bytearray, ranges_orig);
178183
Py_INCREF(obj.ptr());
179184
return obj.ptr();

ddtrace/appsec/_iast/_taint_tracking/Aspects/Helpers.h

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -144,16 +144,18 @@ inline string
144144
mapper_replace(const TaintRangePtr& taint_range, const optional<const py::dict>& new_ranges)
145145
{
146146

147-
if (!taint_range or !new_ranges.has_value() or py::len(new_ranges.value()) == 0) {
147+
if (!taint_range or !new_ranges.has_value() or py::len(*new_ranges) == 0) {
148148
return {};
149149
}
150150

151-
const py::dict& new_ranges_value = new_ranges.value();
151+
// new_ranges.value throws a compile error: 'value' is unavailable: introduced in macOS 10.13
152+
const py::dict& new_ranges_value = *new_ranges;
152153
py::object o = py::cast(taint_range);
153154

154155
if (!new_ranges_value.contains(o)) {
155156
return {};
156157
}
158+
// new_ranges.value throws a compile error: 'value' is unavailable: introduced in macOS 10.13
157159
const TaintRange new_range = py::cast<TaintRange>((*new_ranges)[o]);
158160
return to_string(new_range.get_hash());
159161
}

ddtrace/appsec/_iast/_taint_tracking/TaintTracking/TaintRange.cpp

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -223,13 +223,13 @@ are_all_text_all_ranges(PyObject* candidate_text, const py::tuple& parameter_lis
223223
TaintRangePtr
224224
get_range_by_hash(const size_t range_hash, optional<TaintRangeRefs>& taint_ranges)
225225
{
226-
if (not taint_ranges or taint_ranges->empty()) {
226+
if (not taint_ranges or !taint_ranges.has_value() or taint_ranges->empty()) {
227227
return nullptr;
228228
}
229-
// TODO: Replace this loop with a efficient function, vector.find() is O(n)
230-
// too.
229+
// TODO: Replace this loop with a efficient function, vector.find() is O(n) too.
231230
TaintRangePtr null_range = nullptr;
232-
for (const auto& range : taint_ranges.value()) {
231+
// taint_ranges.value throws a compile error: 'value' is unavailable: introduced in macOS 10.13
232+
for (const auto& range : *taint_ranges) {
233233
if (range_hash == range->get_hash()) {
234234
return range;
235235
}

setup.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -534,7 +534,9 @@ def get_exts_for(name):
534534
)
535535
)
536536

537-
ext_modules.append(CMakeExtension("ddtrace.appsec._iast._taint_tracking._native", source_dir=IAST_DIR))
537+
ext_modules.append(
538+
CMakeExtension("ddtrace.appsec._iast._taint_tracking._native", source_dir=IAST_DIR, optional=False)
539+
)
538540

539541
if (
540542
platform.system() == "Linux" or (platform.system() == "Darwin" and platform.machine() == "arm64")

tests/smoke_test.py

Lines changed: 33 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,11 @@
11
import copy
22
import os
3-
from platform import system
3+
import platform
44
import subprocess
55
import sys
66
import textwrap
77

88

9-
def mac_supported_iast_version():
10-
if system() == "Darwin":
11-
# TODO: MacOS 10.9 or lower has a old GCC version but cibuildwheel has a GCC old version in newest mac versions
12-
# mac_version = [int(i) for i in mac_ver()[0].split(".")]
13-
# mac_version > [10, 9]
14-
return False
15-
return True
16-
17-
189
# Code need to be run in a separate subprocess to reload since reloading .so files doesn't
1910
# work like normal Python ones
2011
test_native_load_code = """
@@ -45,7 +36,7 @@ def emit(self, record):
4536

4637
if __name__ == "__main__":
4738
# ASM IAST smoke test
48-
if sys.version_info >= (3, 6, 0) and system() != "Windows" and mac_supported_iast_version():
39+
if sys.version_info >= (3, 6, 0) and platform.system() != "Windows":
4940
print("Running native IAST module load test...")
5041
test_code = textwrap.dedent(test_native_load_code)
5142
cmd = [sys.executable, "-c", test_code]
@@ -67,7 +58,7 @@ def emit(self, record):
6758
os.environ = orig_env
6859

6960
# ASM WAF smoke test
70-
if system() != "Linux" or sys.maxsize > 2**32:
61+
if platform.system() != "Linux" or sys.maxsize > 2**32:
7162
import ddtrace.appsec._ddwaf
7263
import ddtrace.bootstrap.sitecustomize as module
7364

@@ -80,3 +71,33 @@ def emit(self, record):
8071
else:
8172
# Skip the test for 32-bit Linux systems
8273
print("Skipping test, 32-bit DDWAF not ready yet")
74+
75+
# Profiling smoke test
76+
print("Running profiling smoke test...")
77+
profiling_cmd = [sys.executable, "-c", "import ddtrace.profiling.auto"]
78+
if sys.version_info >= (3, 13, 0):
79+
print("Skipping profiling smoke test for Python 3.13+ as it's not supported yet")
80+
elif (
81+
# echion doesn't work on Windows
82+
platform.system() == "Windows"
83+
# libdatadog x86_64-apple-darwin has not yet been integrated to dd-trace-py
84+
or (platform.system() == "Darwin" and platform.machine() == "x86_64")
85+
# echion crashes on musl linux with Python 3.12 for both x86_64 and
86+
# aarch64
87+
or (platform.system() == "Linux" and sys.version_info[:2] == (3, 12) and platform.libc_ver()[0] != "glibc")
88+
):
89+
orig_env = os.environ.copy()
90+
copied_env = copy.deepcopy(orig_env)
91+
copied_env["DD_PROFILING_STACK_V2_ENABLED"] = "False"
92+
if platform.system() == "Windows":
93+
# Memory profiler crashes on Windows
94+
copied_env["DD_PROFILING_MEMORY_ENABLED"] = "False"
95+
result = subprocess.run(profiling_cmd, env=copied_env, capture_output=True, text=True)
96+
assert result.returncode == 0, "Failed with DD_PROFILING_STACK_V2_ENABLED=0: %s, %s" % (
97+
result.stdout,
98+
result.stderr,
99+
)
100+
else:
101+
result = subprocess.run(profiling_cmd, capture_output=True, text=True)
102+
assert result.returncode == 0, "Failed: %s, %s" % (result.stdout, result.stderr)
103+
print("Profiling smoke test completed successfully")

0 commit comments

Comments
 (0)