Skip to content

Commit 6c4e978

Browse files
committed
Try adding win-conda build chain for testing
1 parent 67baabc commit 6c4e978

2 files changed

Lines changed: 195 additions & 3 deletions

File tree

Lines changed: 195 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,195 @@
1+
# TEMPORARY DIAGNOSTIC WORKFLOW -- DO NOT MERGE TO main.
2+
#
3+
# Reproduces the conda-forge win-64 failure seen on r-filearray-feedstock PR #11:
4+
#
5+
# ** testing if installed package can be loaded from temporary location
6+
# ERROR: loading failed
7+
# *** stack smashing detected ***: terminated
8+
#
9+
# That message is GCC's stack-protector runtime (__stack_chk_fail), not a stack
10+
# overflow. conda-forge's GCC is built with --enable-default-ssp, so
11+
# -fstack-protector-strong is on even though the feedstock's compile lines only
12+
# show "-O2 -Wall -march=x86-64 -mtune=generic".
13+
#
14+
# The install-time abort prints no stack trace, so this workflow does two things:
15+
#
16+
# 1. Bisects the load path. Because tools:::code2LazyLoadDB calls
17+
# loadNamespace(partial = TRUE), the byte-compile step never dyn.load()s the
18+
# DLL -- the test-load step is the FIRST time filearray.dll is ever loaded.
19+
# Only three C++ entry points run: DLL init, getThreads(FALSE), and
20+
# get_float_na().
21+
#
22+
# 2. A/Bs -fno-stack-protector. If the protector-off build loads cleanly, there
23+
# is no real memory corruption and the abort is a toolchain artifact.
24+
25+
name: win-conda-repro
26+
27+
# A workflow_dispatch-only file has to sit on the default branch before the "Run
28+
# workflow" button appears, and this one is not meant to land on main. The push
29+
# trigger is scoped to a throwaway branch so it can be driven from there:
30+
#
31+
# git switch -c diagnose/win-conda && git push -u origin diagnose/win-conda
32+
on:
33+
push:
34+
branches: ['diagnose/win-conda**']
35+
workflow_dispatch:
36+
inputs:
37+
r_version:
38+
description: 'r-base version (feedstock used 4.5.3)'
39+
required: false
40+
default: '4.5.3'
41+
extra_pins:
42+
description: 'Extra conda specs to force the failing toolchain, e.g. "gxx_win-64=15.3.0" "libgcc=16.1.0"'
43+
required: false
44+
default: ''
45+
46+
permissions: read-all
47+
48+
jobs:
49+
repro:
50+
runs-on: windows-latest
51+
52+
name: win-conda (ssp ${{ matrix.ssp }})
53+
54+
strategy:
55+
fail-fast: false
56+
matrix:
57+
ssp: [default, disabled]
58+
59+
defaults:
60+
run:
61+
shell: bash -el {0}
62+
63+
steps:
64+
- uses: actions/checkout@v6
65+
66+
- uses: conda-incubator/setup-miniconda@v3
67+
with:
68+
miniforge-version: latest
69+
channels: conda-forge
70+
channel-priority: strict
71+
72+
# One solve, not an incremental install. Adding the toolchain to an existing
73+
# env is itself implicated in conda-forge/m2w64-sysroot-feedstock#23, and
74+
# keeping that out of the picture keeps the variable count down.
75+
- name: Create environment
76+
env:
77+
R_VERSION: ${{ inputs.r_version }}
78+
EXTRA_PINS: ${{ inputs.extra_pins }}
79+
run: |
80+
# EXTRA_PINS is intentionally unquoted so multiple specs word-split.
81+
conda create -n fa -y -c conda-forge --override-channels \
82+
"r-base=${R_VERSION:-4.5.3}" \
83+
r-rcpp r-bh r-digest r-fastmap r-uuid r-testthat \
84+
gxx_win-64 gcc_win-64 m2-base make \
85+
${EXTRA_PINS}
86+
87+
# Compare this against the build/host lists in the failing feedstock job.
88+
# The failing build compiled with gcc 15.3.0 while r-base and the libgcc /
89+
# libstdcxx runtime were 16.1.0.
90+
- name: Record toolchain
91+
run: |
92+
conda activate fa
93+
echo "::group::conda list"
94+
conda list
95+
echo "::endgroup::"
96+
R --version | head -2
97+
x86_64-w64-mingw32-g++ --version | head -1
98+
x86_64-w64-mingw32-g++ -v 2>&1 | grep -Ei 'thread model|^Target|gcc version' || true
99+
100+
# A fresh checkout carries no build artifacts (src/.gitignore covers
101+
# *.o/*.so/*.dll), but linking stale objects would invalidate the whole
102+
# experiment, so be explicit.
103+
- name: Clean stale objects
104+
run: rm -f src/*.o src/*.so src/*.dll
105+
106+
# --no-test-load so the DLL lands on disk and the bisect below controls
107+
# exactly when it is first loaded.
108+
#
109+
# src/Makevars only sets PKG_CPPFLAGS and there is no Makevars.win, so the
110+
# PKG_CXXFLAGS environment variable reaches the compile rule unopposed.
111+
- name: Install (no test load)
112+
env:
113+
PKG_CXXFLAGS: ${{ matrix.ssp == 'disabled' && '-fno-stack-protector' || '' }}
114+
run: |
115+
conda activate fa
116+
echo "PKG_CXXFLAGS=[${PKG_CXXFLAGS}]"
117+
R CMD INSTALL --no-test-load .
118+
119+
# Directly probative for the "spurious canary" hypothesis: if the guard and
120+
# the failure handler come from a separate runtime DLL, filearray.dll and
121+
# R.dll can disagree about __stack_chk_guard without any buffer ever being
122+
# overrun.
123+
- name: Inspect SSP linkage
124+
run: |
125+
set +e
126+
conda activate fa
127+
R --vanilla -s -e 'cat(list.files(system.file("libs", package = "filearray"), pattern = "filearray[.]dll$", recursive = TRUE, full.names = TRUE)[1])' > dll.txt
128+
DLL=$(cat dll.txt)
129+
echo "DLL = $DLL"
130+
OBJDUMP=$(command -v x86_64-w64-mingw32-objdump || command -v objdump)
131+
echo "objdump = $OBJDUMP"
132+
echo "::group::imported DLLs"
133+
"$OBJDUMP" -p "$DLL" | grep -i 'DLL Name'
134+
echo "::endgroup::"
135+
echo "::group::stack_chk / ssp symbols"
136+
"$OBJDUMP" -x "$DLL" | grep -i 'stack_chk\|ssp'
137+
echo "::endgroup::"
138+
139+
- name: Write bisect scripts
140+
run: |
141+
cat > step_a.R <<'EOF'
142+
dll <- list.files(system.file("libs", package = "filearray"),
143+
pattern = "filearray[.]dll$", recursive = TRUE, full.names = TRUE)[1]
144+
dyn.load(dll)
145+
cat("A: dyn.load OK\n")
146+
EOF
147+
148+
cat > step_c.R <<'EOF'
149+
dll <- list.files(system.file("libs", package = "filearray"),
150+
pattern = "filearray[.]dll$", recursive = TRUE, full.names = TRUE)[1]
151+
dyn.load(dll)
152+
print(.Call("_filearray_get_float_na"))
153+
cat("C: get_float_na OK\n")
154+
EOF
155+
156+
cat > step_b.R <<'EOF'
157+
dll <- list.files(system.file("libs", package = "filearray"),
158+
pattern = "filearray[.]dll$", recursive = TRUE, full.names = TRUE)[1]
159+
dyn.load(dll)
160+
print(.Call("_filearray_getThreads", FALSE))
161+
cat("B: getThreads OK\n")
162+
EOF
163+
164+
cat > step_d.R <<'EOF'
165+
library(filearray)
166+
cat("D: library OK\n")
167+
EOF
168+
169+
# One process per step. A canary abort kills the process, so the last "OK"
170+
# line printed names the last step that survived.
171+
#
172+
# A = static ctors + R_init_filearray (src/RcppExports.cpp:440)
173+
# C = get_float_na (src/conversion.cpp:450)
174+
# B = getThreads (src/threadSettings.cpp:16)
175+
# -> tthread::thread::hardware_concurrency()
176+
# -> SYSTEM_INFO si; GetSystemInfo(&si)
177+
# (inst/include/tthread/tinythread.h:981)
178+
# the only stack-buffer write anywhere on the load path
179+
# D = full .onLoad + .onAttach
180+
- name: Bisect the load path
181+
run: |
182+
set +e
183+
conda activate fa
184+
for s in a c b d; do
185+
echo "--- step ${s} ---"
186+
R --vanilla -s -f "step_${s}.R"
187+
echo "exit=$?"
188+
done
189+
190+
- name: Run tests (only meaningful if step D passed)
191+
run: |
192+
set +e
193+
conda activate fa
194+
R --vanilla -s -e 'library(testthat); library(filearray); test_dir("tests/testthat", stop_on_failure = FALSE)'
195+
echo "exit=$?"

CRAN-SUBMISSION

Lines changed: 0 additions & 3 deletions
This file was deleted.

0 commit comments

Comments
 (0)