Skip to content

Commit f8fcd30

Browse files
committed
Fix playground CORS by deploying to gh-pages branch
Problem: GitHub Releases don't serve files with CORS headers, blocking browser fetch() requests. CORS proxies are unreliable for large files. Solution: Deploy playground to gh-pages branch with WASM files included. - Main branch stays clean (no 63MB WASM files) - Workflow downloads WASM from latest release - Pushes to separate gh-pages branch (orphan, separate history) - GitHub Pages serves with proper CORS headers - Same-origin = no CORS issues Changes: - Updated deploy workflow to download WASM and push to gh-pages - Simplified index.html to use local files (served via Pages) - Added clangd files to .gitignore - Added ?prod=1 URL param for local testing
1 parent 8a2a60f commit f8fcd30

File tree

5 files changed

+98
-49
lines changed

5 files changed

+98
-49
lines changed

.github/workflows/deploy-playground.yml

Lines changed: 58 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -7,32 +7,71 @@ on:
77
workflow_dispatch:
88

99
permissions:
10-
contents: read
11-
pages: write
12-
id-token: write
13-
14-
concurrency:
15-
group: "pages"
16-
cancel-in-progress: false
10+
contents: write
1711

1812
jobs:
1913
deploy:
20-
environment:
21-
name: github-pages
22-
url: ${{ steps.deployment.outputs.page_url }}
2314
runs-on: ubuntu-latest
2415
steps:
2516
- name: Checkout
2617
uses: actions/checkout@v4
18+
with:
19+
fetch-depth: 0
2720

28-
- name: Setup Pages
29-
uses: actions/configure-pages@v4
21+
- name: Download latest WASM files from release
22+
run: |
23+
# Get the latest playground release
24+
LATEST_RELEASE=$(curl -s https://api.github.com/repos/${{ github.repository }}/releases | \
25+
jq -r '[.[] | select(.tag_name | startswith("playground-v"))] | first | .tag_name')
3026
31-
- name: Upload artifact
32-
uses: actions/upload-pages-artifact@v3
33-
with:
34-
path: 'nullsafe-playground'
27+
echo "Latest playground release: $LATEST_RELEASE"
28+
29+
if [ -z "$LATEST_RELEASE" ]; then
30+
echo "No playground release found! Please create a release with tag 'playground-vX.X.X'"
31+
exit 1
32+
fi
33+
34+
# Download the WASM files into playground directory
35+
cd nullsafe-playground
36+
curl -L -o clang.wasm "https://github.com/${{ github.repository }}/releases/download/${LATEST_RELEASE}/clang-nullsafe.wasm"
37+
curl -L -o clang.js "https://github.com/${{ github.repository }}/releases/download/${LATEST_RELEASE}/clang-nullsafe.js"
38+
curl -L -o clangd.wasm "https://github.com/${{ github.repository }}/releases/download/${LATEST_RELEASE}/clangd-nullsafe.wasm"
39+
curl -L -o clangd.js "https://github.com/${{ github.repository }}/releases/download/${LATEST_RELEASE}/clangd-nullsafe.js"
40+
41+
echo "Downloaded files:"
42+
ls -lh clang.wasm clang.js clangd.wasm clangd.js
43+
44+
- name: Deploy to gh-pages
45+
run: |
46+
git config user.name "github-actions[bot]"
47+
git config user.email "github-actions[bot]@users.noreply.github.com"
48+
49+
# Check if gh-pages exists
50+
if git ls-remote --exit-code --heads origin gh-pages; then
51+
echo "gh-pages branch exists, checking out"
52+
git fetch origin gh-pages
53+
git checkout gh-pages
54+
else
55+
echo "Creating new orphan gh-pages branch"
56+
git checkout --orphan gh-pages
57+
fi
58+
59+
# Clear everything
60+
git rm -rf . 2>/dev/null || true
61+
62+
# Copy playground files
63+
git checkout null-safe-c-dev -- nullsafe-playground
64+
mv nullsafe-playground/* .
65+
rm -rf nullsafe-playground
66+
67+
# Add all files (including the downloaded WASM)
68+
git add -A
69+
70+
# Commit
71+
if git diff --staged --quiet; then
72+
echo "No changes to deploy"
73+
else
74+
git commit -m "Deploy playground from ${{ github.sha }}"
75+
git push origin gh-pages
76+
fi
3577
36-
- name: Deploy to GitHub Pages
37-
id: deployment
38-
uses: actions/deploy-pages@v4

clang/lib/Sema/SemaExpr.cpp

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14925,12 +14925,9 @@ static QualType CheckIndirectionOperand(Sema &S, Expr *Op, ExprValueKind &VK,
1492514925

1492614926
if (auto Nullability = CheckType->getNullability()) {
1492714927
if (*Nullability == NullabilityKind::Nullable) {
14928-
// strict-nullability: Allow nullable dereferences in condition contexts (if/while/for).
14929-
// The dereference itself performs a null-check, so it's safe.
14930-
// The narrowing analysis will collect these and narrow the pointer for the body.
14931-
if (S.InConditionContext == 0) {
14932-
S.Diag(OpLoc, diag::warn_strict_nullability_dereference) << OpTy;
14933-
}
14928+
// strict-nullability: Warn about dereferencing nullable pointers.
14929+
// Dereferencing does NOT perform a null-check - it will crash if null!
14930+
S.Diag(OpLoc, diag::warn_strict_nullability_dereference) << OpTy;
1493414931
}
1493514932
}
1493614933
}

clang/test/Sema/strict-nullability.c

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -475,24 +475,24 @@ void test_while_and_narrowing(const unsigned char* input_pointer,
475475
}
476476
}
477477

478-
// Test: dereference in while condition (condition context)
478+
// Test: dereference in while condition requires null check first
479479
void test_while_deref_condition(const char *ptr) {
480-
while (*ptr != '\0') { // OK - dereference allowed in condition
480+
while (*ptr != '\0') { // expected-warning {{dereferencing nullable pointer}}
481481
ptr++;
482482
}
483483
}
484484

485-
// Test: dereference in for condition (condition context)
485+
// Test: dereference in for condition requires null check first
486486
void test_for_deref_condition(const char *input) {
487487
const char *ptr;
488-
for (ptr = input; *ptr != '\0'; ptr++) { // OK - dereference allowed in condition
488+
for (ptr = input; *ptr != '\0'; ptr++) { // expected-warning {{dereferencing nullable pointer}}
489489
// loop body
490490
}
491491
}
492492

493-
// Test: dereference in if condition (condition context)
493+
// Test: dereference in if condition requires null check first
494494
void test_if_deref_condition(const char *str) {
495-
if (*str == 'a') { // OK - dereference allowed in condition
495+
if (*str == 'a') { // expected-warning {{dereferencing nullable pointer}}
496496
// then branch
497497
}
498498
}
@@ -501,17 +501,17 @@ void test_if_deref_condition(const char *str) {
501501
int is_digit_pure(char c) __attribute__((const));
502502

503503
void test_pure_function_preserves_narrowing(const char *zDate) {
504-
if (!is_digit_pure(*zDate)) { // dereference in condition - narrows zDate to non-null
504+
if (!is_digit_pure(*zDate)) { // expected-warning {{dereferencing nullable pointer}}
505505
return;
506506
}
507-
char val = *zDate - '0'; // Should be OK - zDate still non-null after pure function call
507+
char val = *zDate - '0'; // OK after dereference above - if we got here, zDate is non-null
508508
}
509509

510510
// Test: narrowing invalidated after impure function call
511511
int is_digit_impure(char c);
512512

513513
void test_impure_function_invalidates_narrowing(const char *zDate) {
514-
if (!is_digit_impure(*zDate)) { // dereference in condition - narrows zDate to non-null
514+
if (!is_digit_impure(*zDate)) { // expected-warning {{dereferencing nullable pointer}}
515515
return;
516516
}
517517
char val = *zDate - '0'; // expected-warning {{dereferencing nullable pointer}}
@@ -521,10 +521,10 @@ void test_impure_function_invalidates_narrowing(const char *zDate) {
521521
int is_also_pure(char c) __attribute__((pure));
522522

523523
void test_pure_attr_preserves_narrowing(const char *zDate) {
524-
if (!is_also_pure(*zDate)) { // dereference in condition - narrows zDate to non-null
524+
if (!is_also_pure(*zDate)) { // expected-warning {{dereferencing nullable pointer}}
525525
return;
526526
}
527-
char val = *zDate - '0'; // Should be OK - zDate still non-null after pure function call
527+
char val = *zDate - '0'; // OK after dereference above - if we got here, zDate is non-null
528528
}
529529

530530
// ============================================================================

nullsafe-playground/.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
11
# Generated WASM files (too large to commit, will be built from source or downloaded from releases)
22
clang.wasm
33
clang.js
4+
clangd.wasm
5+
clangd.js
46
clang-nullsafe.wasm
57
clang-nullsafe.js
8+
clangd-nullsafe.wasm
9+
clangd-nullsafe.js
610
clang-mainline.wasm
711
clang-mainline.js
812

nullsafe-playground/index.html

Lines changed: 22 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -573,7 +573,7 @@ <h1>Null-Safe Clang Playground</h1>
573573
<option value="and-pattern">7. AND Pattern Narrowing</option>
574574
<option value="else-branch">8. Else Branch Narrowing</option>
575575
<option value="loop-narrowing">9. Loop Condition Narrowing</option>
576-
<option value="dereference-context">10. Dereference in Condition</option>
576+
<option value="dereference-context">10. Null Check Before Deref</option>
577577
</select>
578578

579579
<button id="shareBtn" class="btn btn-secondary">
@@ -677,8 +677,8 @@ <h1>Null-Safe Clang Playground</h1>
677677
int is_digit(char c) __attribute__((const));
678678
679679
void parse(const char* str) {
680-
if (is_digit(*str)) { // narrows str
681-
char val = *str - '0'; // OK - still narrowed
680+
if (str && is_digit(*str)) { // Check first, then call
681+
char val = *str - '0'; // OK - narrowed by check
682682
}
683683
}`,
684684

@@ -769,22 +769,22 @@ <h1>Null-Safe Clang Playground</h1>
769769
}
770770
}`,
771771

772-
'dereference-context': `// Dereference in condition is allowed
772+
'dereference-context': `// Must null-check before dereferencing
773773
void while_str(const char* ptr) {
774-
while (*ptr != '\\0') { // OK - can deref
774+
while (ptr && *ptr != '\\0') { // Check first!
775775
ptr++;
776776
}
777777
}
778778
779779
void if_check(const char* str) {
780-
if (*str == 'a') { // OK - can deref
780+
if (str && *str == 'a') { // Check first!
781781
// process...
782782
}
783783
}
784784
785785
void for_check(const char* input) {
786786
const char* p;
787-
for (p = input; *p; p++) { // OK - can deref
787+
for (p = input; p && *p; p++) { // Check first!
788788
// process...
789789
}
790790
}`
@@ -1118,13 +1118,22 @@ <h1>Null-Safe Clang Playground</h1>
11181118
try {
11191119
// Determine script URL
11201120
const isDev = window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1';
1121-
scriptUrl = isDev
1122-
? './clang.js'
1123-
: 'https://github.com/cs01/llvm-project/releases/latest/download/clang-nullsafe.js';
11241121

1125-
const wasmUrl = isDev
1126-
? './clang.wasm'
1127-
: 'https://github.com/cs01/llvm-project/releases/latest/download/clang-nullsafe.wasm';
1122+
// Allow testing production URLs on localhost with ?prod=1
1123+
const urlParams = new URLSearchParams(window.location.search);
1124+
const forceProd = urlParams.get('prod') === '1' || urlParams.get('mode') === 'prod';
1125+
1126+
if (isDev && !forceProd) {
1127+
// Local development - files should be in the same directory
1128+
scriptUrl = './clang.js';
1129+
var wasmUrl = './clang.wasm';
1130+
console.log('🔧 Development mode: Using local files');
1131+
} else {
1132+
// Production - files are served from GitHub Pages (same origin, no CORS issues!)
1133+
scriptUrl = './clang.js';
1134+
var wasmUrl = './clang.wasm';
1135+
console.log('🚀 Production mode: Using files from GitHub Pages');
1136+
}
11281137

11291138
// Pre-load the WASM binary ONCE with progress tracking
11301139
console.log('Downloading WASM binary...');

0 commit comments

Comments
 (0)