Compare commits
No commits in common. "76e4d0c391db937f33c8776b7440d7470a2af023" and "e45a717ce78d9d4f8be9c540d412b684c4713c56" have entirely different histories.
76e4d0c391
...
e45a717ce7
6 changed files with 7 additions and 5839 deletions
|
|
@ -1,341 +0,0 @@
|
|||
name: sms
|
||||
|
||||
# Phase 0 of the SMS remediation plan: make the SMS stack verifiable.
|
||||
#
|
||||
# Before this file existed, `crates/robius-sms` (2,019 LOC) and
|
||||
# `crates/apps/nigig-sms` (5,706 LOC) had NO CI of any kind and two tests,
|
||||
# both of which assert derived trait impls and neither of which mentions
|
||||
# SMS. That is 7,725 lines of untested code whose job is to spend the
|
||||
# user's money by sending real, billable, irreversible messages.
|
||||
#
|
||||
# Enforces:
|
||||
# 1. Both crates compile on the host AND on aarch64-linux-android.
|
||||
# The host build only ever compiles sys/linux.rs -- a stub whose
|
||||
# every function returns PermanentlyUnavailable. ALL of the real
|
||||
# logic (~600 lines of JNI in sys/android/) is behind
|
||||
# #[cfg(target_os = "android")] and is invisible to a host build.
|
||||
# A host-only gate would be close to worthless here.
|
||||
# 2. Clippy stays clean for code these two crates OWN. Warnings from
|
||||
# path dependencies (nigig-core, nigig-uikit, matrix_client) are
|
||||
# pre-existing and out of scope for this workflow; they are
|
||||
# filtered by package id rather than muted, so they still show in
|
||||
# the log and a future workflow can gate them.
|
||||
# 3. Tests pass.
|
||||
# 4. Two defect classes that are invisible in review and have already
|
||||
# shipped here, as source-scanning gates.
|
||||
#
|
||||
# `cargo fmt --check` is deliberately not a gate, matching the reasoning
|
||||
# already recorded in doc-engine.yml: these crates predate the pinned
|
||||
# toolchain's rustfmt style and reformatting them wholesale would
|
||||
# conflict with the remediation work. A step that always fails gets
|
||||
# ignored, which is worse than no step.
|
||||
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- 'crates/robius-sms/**'
|
||||
- 'crates/apps/nigig-sms/**'
|
||||
- 'Cargo.lock'
|
||||
- 'Cargo.toml'
|
||||
- 'rust-toolchain.toml'
|
||||
- '.forgejo/workflows/sms.yml'
|
||||
pull_request:
|
||||
paths:
|
||||
- 'crates/robius-sms/**'
|
||||
- 'crates/apps/nigig-sms/**'
|
||||
- 'Cargo.lock'
|
||||
- 'Cargo.toml'
|
||||
- 'rust-toolchain.toml'
|
||||
- '.forgejo/workflows/sms.yml'
|
||||
|
||||
jobs:
|
||||
# ---------------------------------------------------------------------
|
||||
# Source-scanning gates. These need no toolchain, so they run first and
|
||||
# fail fast.
|
||||
# ---------------------------------------------------------------------
|
||||
gates:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
# A poisoned mutex turns every later `.lock().unwrap()` into a
|
||||
# panic. The contact cache in conversations_list.rs is written from
|
||||
# a spawned worker thread, so one panic anywhere under the lock
|
||||
# permanently bricks contact resolution for the process lifetime,
|
||||
# and it surfaces far from the cause.
|
||||
#
|
||||
# The codebase already knows the right pattern -- companies_list.rs
|
||||
# uses `if let Ok(mut s) = ..lock()` -- it is just applied
|
||||
# inconsistently. This keeps the good pattern from regressing.
|
||||
#
|
||||
# RATCHET. There are 19 source lines with these today (bug A6 in the
|
||||
# plan); fixing them is Phase A work, not Phase 0. A gate that
|
||||
# fails on its first run gets switched off, so this asserts the
|
||||
# count never RISES and must be lowered as they are fixed. When it
|
||||
# reaches 0, replace the whole step with a plain grep that fails on
|
||||
# any match.
|
||||
- name: No new .lock().unwrap() in the SMS crates
|
||||
run: |
|
||||
set -euo pipefail
|
||||
BASELINE=19
|
||||
count=$(grep -rn --include='*.rs' '\.lock()\s*\.unwrap()' \
|
||||
crates/robius-sms/src crates/apps/nigig-sms/src \
|
||||
| sed 's/^[^:]*:[0-9]*://' \
|
||||
| grep -vcE '^[[:space:]]*(//|/\*|\*)' || true)
|
||||
echo "found $count, baseline $BASELINE"
|
||||
if [ "$count" -gt "$BASELINE" ]; then
|
||||
grep -rn --include='*.rs' '\.lock()\s*\.unwrap()' \
|
||||
crates/robius-sms/src crates/apps/nigig-sms/src || true
|
||||
echo
|
||||
echo "ERROR: $count .lock().unwrap() calls, up from $BASELINE."
|
||||
echo "These panic on a poisoned mutex. One panic while the lock"
|
||||
echo "is held bricks the cache for the rest of the process. Use:"
|
||||
echo " if let Ok(guard) = MUTEX.lock() { .. }"
|
||||
echo "or MUTEX.lock().unwrap_or_else(|e| e.into_inner())"
|
||||
exit 1
|
||||
fi
|
||||
if [ "$count" -lt "$BASELINE" ]; then
|
||||
echo
|
||||
echo "Good: down to $count. Lower BASELINE in this file to $count"
|
||||
echo "so the progress cannot be undone."
|
||||
exit 1
|
||||
fi
|
||||
echo "OK"
|
||||
|
||||
# `&s[..n]` where n is a byte offset panics with "byte index N is
|
||||
# not a char boundary" the moment n lands inside a multi-byte
|
||||
# character. truncate_preview() in sms_utils.rs did exactly this
|
||||
# against a 120-BYTE constant, and it runs per visible row per
|
||||
# frame -- so one inbound SMS containing emoji, Swahili or Arabic
|
||||
# text panicked the whole conversation list on every frame. That is
|
||||
# a remote denial of service triggerable by anyone who knows the
|
||||
# victim's number.
|
||||
#
|
||||
# Slicing must go through char_indices()/char_boundary logic.
|
||||
#
|
||||
# RATCHET, for the same reason as the step above: the one live
|
||||
# instance is truncate_preview() in sms_utils.rs, and fixing it is
|
||||
# bug A3 in the remediation plan. Drop BASELINE to 0 with that fix
|
||||
# and this becomes a hard gate.
|
||||
- name: No new byte-offset string slicing in SMS text helpers
|
||||
run: |
|
||||
set -euo pipefail
|
||||
BASELINE=1
|
||||
count=$(grep -rnE --include='*.rs' \
|
||||
'&[A-Za-z_][A-Za-z0-9_]*\[\.\.[A-Za-z0-9_]+\]' \
|
||||
crates/apps/nigig-sms/src \
|
||||
| sed 's/^[^:]*:[0-9]*://' \
|
||||
| grep -vcE '^[[:space:]]*(//|/\*|\*)' || true)
|
||||
echo "found $count, baseline $BASELINE"
|
||||
if [ "$count" -gt "$BASELINE" ]; then
|
||||
grep -rnE --include='*.rs' \
|
||||
'&[A-Za-z_][A-Za-z0-9_]*\[\.\.[A-Za-z0-9_]+\]' \
|
||||
crates/apps/nigig-sms/src || true
|
||||
echo
|
||||
echo "ERROR: $count byte-offset slice(s), up from $BASELINE."
|
||||
echo "These index a &str by BYTE offset and panic when the"
|
||||
echo "offset is not a char boundary -- reachable from any"
|
||||
echo "inbound SMS containing emoji or non-Latin text. Use"
|
||||
echo "char_indices() to find a real boundary first."
|
||||
exit 1
|
||||
fi
|
||||
if [ "$count" -lt "$BASELINE" ]; then
|
||||
echo
|
||||
echo "Good: down to $count. Lower BASELINE in this file to $count."
|
||||
exit 1
|
||||
fi
|
||||
echo "OK"
|
||||
|
||||
# ---------------------------------------------------------------------
|
||||
# robius-sms: the platform layer. Cheap -- no GUI stack.
|
||||
# ---------------------------------------------------------------------
|
||||
robius-sms:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
# polkit/gio are pulled in ONLY by the Linux backend, whose every
|
||||
# function returns PermanentlyUnavailable. They are why
|
||||
# deny-nigig-build.toml carries RUSTSEC-2024-0370 and
|
||||
# RUSTSEC-2024-0429 plus an open LGPL-2.1 question. Phase E9 of the
|
||||
# remediation plan removes the backend and these packages with it;
|
||||
# until then CI needs the headers to build the stub.
|
||||
- name: Install native dependencies
|
||||
run: |
|
||||
sudo apt-get update -qq
|
||||
sudo apt-get install -y -qq \
|
||||
pkg-config libpolkit-gobject-1-dev libpolkit-agent-1-dev \
|
||||
libglib2.0-dev libsqlite3-dev
|
||||
|
||||
- name: Host check
|
||||
run: cargo check --locked -p robius-sms
|
||||
|
||||
- name: Host clippy
|
||||
run: cargo clippy --locked -p robius-sms --all-targets -- -D warnings
|
||||
|
||||
- name: Host tests
|
||||
run: cargo test --locked -p robius-sms
|
||||
|
||||
# ---------------------------------------------------------------------
|
||||
# The Android target. This is the job that matters: everything in
|
||||
# sys/android/ is invisible to every other job in this file.
|
||||
# ---------------------------------------------------------------------
|
||||
android:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 40
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
# d8 (build-tools 34) rejects class file major version > 61, i.e.
|
||||
# anything newer than Java 17. A runner defaulting to JDK 21 makes
|
||||
# build.rs fail inside d8 with "Unsupported class file major
|
||||
# version 65", which reads like a toolchain bug rather than a JDK
|
||||
# mismatch. Pin the JDK so that failure cannot recur here.
|
||||
- uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: temurin
|
||||
java-version: '17'
|
||||
|
||||
- uses: android-actions/setup-android@v3
|
||||
|
||||
- name: Install Android SDK packages
|
||||
run: sdkmanager "platforms;android-34" "build-tools;34.0.0"
|
||||
|
||||
- name: Add Rust Android target
|
||||
run: rustup target add aarch64-linux-android
|
||||
|
||||
# robius-sms/src/build.rs compiles two .java files with javac and
|
||||
# dexes them with d8, so this step exercises the Java toolchain and
|
||||
# the runtime-dex-loading path, not just the Rust.
|
||||
- name: Android check
|
||||
run: cargo check --locked -p robius-sms --target aarch64-linux-android
|
||||
|
||||
- name: Android clippy
|
||||
run: |
|
||||
cargo clippy --locked -p robius-sms \
|
||||
--target aarch64-linux-android -- -D warnings
|
||||
|
||||
# ---------------------------------------------------------------------
|
||||
# nigig-sms: the UI layer. Pulls the whole Makepad stack, so it is the
|
||||
# slow job and needs the GUI system libraries.
|
||||
# ---------------------------------------------------------------------
|
||||
nigig-sms:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Install native dependencies
|
||||
run: |
|
||||
sudo apt-get update -qq
|
||||
sudo apt-get install -y -qq \
|
||||
pkg-config libwayland-dev libxcursor-dev libxrandr-dev \
|
||||
libxi-dev libx11-dev libgl1-mesa-dev libasound2-dev \
|
||||
libpolkit-gobject-1-dev libpolkit-agent-1-dev \
|
||||
libglib2.0-dev libssl-dev libsqlite3-dev libudev-dev \
|
||||
libpulse-dev libxkbcommon-dev
|
||||
|
||||
- name: Check
|
||||
run: cargo check --locked -p nigig-sms
|
||||
|
||||
- name: Test
|
||||
run: cargo test --locked -p nigig-sms
|
||||
|
||||
# `-D warnings` cannot be applied to the whole `-p nigig-sms` build:
|
||||
# it also compiles nigig-core, nigig-uikit and matrix_client, which
|
||||
# carry ~89 pre-existing warnings that are not this workflow's to
|
||||
# fix. Muting them with --no-deps does not work either -- they are
|
||||
# workspace members, not registry deps, so clippy still reports
|
||||
# them.
|
||||
#
|
||||
# So: take clippy's JSON, keep only diagnostics whose package id is
|
||||
# nigig-sms, and count those. Dependency warnings stay visible in
|
||||
# the log above but do not fail the build. When nigig-core and
|
||||
# nigig-uikit are cleaned up, this can collapse to a plain
|
||||
# `-- -D warnings`.
|
||||
#
|
||||
# RATCHET at 50. Every one is mechanical (unused imports, dead
|
||||
# code, map->for_each, needless borrows) and none is a logic
|
||||
# change, but `cargo clippy --fix` cannot apply them: the crate is
|
||||
# built around Makepad's script_mod! proc macro and rustfix
|
||||
# refuses to edit through it. They therefore have to be fixed by
|
||||
# hand, which is Phase F work, not Phase 0.
|
||||
#
|
||||
# 34 of the 50 are dead-code reports for the duplicate contact
|
||||
# subsystem in inbox/sms_screen.rs -- an entire second copy of the
|
||||
# cache, its lookup helpers and try_load_contacts(), none of it
|
||||
# reachable. Deleting that file's dead half clears most of this
|
||||
# number in one commit.
|
||||
- name: Clippy ratchet (nigig-sms-owned diagnostics only)
|
||||
run: |
|
||||
set -euo pipefail
|
||||
BASELINE=50
|
||||
cargo clippy --locked -p nigig-sms --all-targets \
|
||||
--message-format=json > /tmp/clippy-sms.json 2>/tmp/clippy-sms.err || true
|
||||
# Surface the human-readable log for debugging.
|
||||
cat /tmp/clippy-sms.err || true
|
||||
BASELINE="$BASELINE" python3 - <<'PY'
|
||||
import json, os, sys
|
||||
baseline = int(os.environ['BASELINE'])
|
||||
owned, seen = [], set()
|
||||
with open('/tmp/clippy-sms.json') as fh:
|
||||
for line in fh:
|
||||
try:
|
||||
m = json.loads(line)
|
||||
except ValueError:
|
||||
continue
|
||||
if m.get('reason') != 'compiler-message':
|
||||
continue
|
||||
if 'nigig-sms' not in m.get('package_id', ''):
|
||||
continue
|
||||
msg = m['message']
|
||||
if msg.get('level') not in ('warning', 'error'):
|
||||
continue
|
||||
# --all-targets compiles lib and lib-test, duplicating
|
||||
# every diagnostic; dedupe on rendered text.
|
||||
key = msg.get('rendered', '')
|
||||
if key in seen:
|
||||
continue
|
||||
seen.add(key)
|
||||
owned.append(msg)
|
||||
n = len(owned)
|
||||
print("found %d nigig-sms diagnostics, baseline %d" % (n, baseline))
|
||||
if n > baseline:
|
||||
for msg in owned:
|
||||
sys.stdout.write(msg.get('rendered', ''))
|
||||
print()
|
||||
print("ERROR: %d diagnostics, up from %d." % (n, baseline))
|
||||
sys.exit(1)
|
||||
if n < baseline:
|
||||
print()
|
||||
print("Good: down to %d. Lower BASELINE in this file to %d "
|
||||
"so the progress cannot be undone." % (n, n))
|
||||
sys.exit(1)
|
||||
print("OK")
|
||||
PY
|
||||
|
||||
# ---------------------------------------------------------------------
|
||||
# Supply chain. Mirrors the gates already in nigig-build.yml so the SMS
|
||||
# crates cannot drift from the rest of the workspace.
|
||||
# ---------------------------------------------------------------------
|
||||
supply-chain:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 15
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
# A lockfile that changes during CI means the committed one was
|
||||
# stale. This is only enforceable because Phase 0.1 committed the
|
||||
# root Cargo.lock -- before that it was gitignored and every build
|
||||
# silently re-resolved.
|
||||
- name: Lockfile must be committed and current
|
||||
run: |
|
||||
set -euo pipefail
|
||||
test -f Cargo.lock || { echo "missing Cargo.lock at workspace root"; exit 1; }
|
||||
cargo metadata --locked --format-version 1 > /dev/null
|
||||
git diff --exit-code -- Cargo.lock
|
||||
|
||||
- name: Reject whitespace errors
|
||||
run: git diff --check
|
||||
8
.gitignore
vendored
8
.gitignore
vendored
|
|
@ -1,11 +1,5 @@
|
|||
target
|
||||
# Phase 0.1: the workspace root lockfile is TRACKED. CI runs with --locked
|
||||
# (see .forgejo/workflows/*.yml), which cannot work against an ignored
|
||||
# lockfile: `cargo metadata --locked` fails outright and every build
|
||||
# re-resolves, so a dependency can change under CI without any commit.
|
||||
# Nested/vendored lockfiles stay ignored via the pattern below.
|
||||
**/Cargo.lock
|
||||
!/Cargo.lock
|
||||
Cargo.lock
|
||||
# Review item 1.1: the payment crates are validated standalone against a
|
||||
# checked-in lockfile, so their locks must be tracked. Application crates
|
||||
# keep using the ignore rule above.
|
||||
|
|
|
|||
5487
Cargo.lock
generated
5487
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
|
|
@ -29,8 +29,10 @@ pub extern "C" fn Java_robius_sms_SmsBootReceiver_rustRestoreSchedules<'a>(
|
|||
_: JObject<'a>,
|
||||
context: JObject<'a>,
|
||||
) {
|
||||
unsafe {
|
||||
let mut env = env;
|
||||
let _ = super::schedule::restore_all_schedules(&mut env, &context);
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn ensure_loaded(env: &mut JNIEnv<'_>) -> Result<()> {
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ pub(crate) fn cancel_scheduled_sms(id: i32) -> Result<()> {
|
|||
}
|
||||
|
||||
pub(crate) fn list_scheduled_sms() -> Result<Vec<ScheduledMessage>> {
|
||||
robius_android_env::with_activity(load_schedules)
|
||||
robius_android_env::with_activity(|env, activity| load_schedules(env, activity))
|
||||
.map_err(|_| Error::AndroidEnvironment)
|
||||
.and_then(|x| x)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ use jni::{
|
|||
use crate::{Error, Result, SmsThread};
|
||||
|
||||
pub(crate) fn list_threads() -> Result<Vec<SmsThread>> {
|
||||
robius_android_env::with_activity(list_threads_inner)
|
||||
robius_android_env::with_activity(|env, activity| list_threads_inner(env, activity))
|
||||
.map_err(|_| Error::AndroidEnvironment)
|
||||
.and_then(|x| x)
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue