From 90758a14d0a74190e0eb4f29ae811a17fdd4a7a5 Mon Sep 17 00:00:00 2001 From: Jakob Nybo Andersen Date: Thu, 12 Feb 2026 08:07:08 +0100 Subject: [PATCH] Bugfix: Negative bytes to readbytes! --- .github/workflows/tagbot.yml | 25 ++++++++++++++++++++ AGENTS.md | 45 ++++++++++++++++++++++++++++++++++++ CLAUDE.md | 42 +-------------------------------- src/io.jl | 1 + test/runtests.jl | 3 +++ 5 files changed, 75 insertions(+), 41 deletions(-) create mode 100644 .github/workflows/tagbot.yml create mode 100644 AGENTS.md diff --git a/.github/workflows/tagbot.yml b/.github/workflows/tagbot.yml new file mode 100644 index 0000000..ae4d242 --- /dev/null +++ b/.github/workflows/tagbot.yml @@ -0,0 +1,25 @@ +name: TagBot +on: + issue_comment: + types: + - created + workflow_dispatch: + inputs: + lookback: + description: "[DEPRECATED] No longer has any effect" + default: "3" +jobs: + TagBot: + if: github.event_name == 'workflow_dispatch' || github.actor == 'JuliaTagBot' + runs-on: ubuntu-slim + steps: + - uses: JuliaRegistries/TagBot@v1 + with: + token: ${{ secrets.GITHUB_TOKEN }} + # For commits that modify workflow files: SSH key enables tagging, but + # releases require manual creation. For full automation of such commits, + # use a PAT with `workflow` scope instead of GITHUB_TOKEN. + # See: https://github.com/JuliaRegistries/TagBot#commits-that-modify-workflow-files + ssh: ${{ secrets.DOCUMENTER_KEY }} + # ssh: ${{ secrets.NAME_OF_MY_SSH_PRIVATE_KEY_SECRET }} + # changelog_format: github # 'custom' (default), 'github', or 'conventional' diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..4c8c9f8 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,45 @@ +# AGENTS.md + +This file provides guidance to LLMs when working with code in this repository. + +## Project Overview + +MemoryViews.jl provides `MemoryView`, a low-level view into `Memory{T}` for Julia ≥ 1.11. It's a `DenseVector{T}` subtype representing a `MemoryRef{T}` + length, with static mutability tracking via type parameter (`Mutable`/`Immutable`). The package also defines the `MemoryKind` trait for dispatch on memory-backed types. + +## Commands + +```bash +# Run tests +JULIA_TEST_FAILFAST=true julia --startup=no --project -e 'using Pkg; Pkg.test()' + +# Format code +runic -i . +``` + +When running in test mode, Julia has boundscheck always enabled. When running normally, +out-of-bounds access in functions marked `@inbounds` is undefined behaviour. +Set `--check-bounds=yes` to force boundschecking when running experiments. + +## Architecture + +**Core types** (defined in `src/MemoryViews.jl`): +- `MemoryView{T, M}` where `M ∈ {Mutable, Immutable}` — the main type +- `MemoryKind` trait: `IsMemory{T}` / `NotMemory` for dispatch + +**Source files**: +- `construction.jl` — constructors from Array, Memory, String, SubArray, CodeUnits +- `basic.jl` — indexing, slicing (returns views, not copies), copying, find operations with memchr/memrchr C calls, comparison via memcmp +- `experimental.jl` — `split_first`, `split_last`, `split_at`, `split_unaligned` +- `delimited.jl` — `split_each` delimiter iterator +- `base_arrays.jl` — Vector/Memory conversion, append +- `io.jl` — `readbytes!` + +**Extensions** (`ext/`): StringViews, FixedSizeArrays, LibDeflate integration. + +## Key Patterns + +- Slicing creates views into the same memory (no allocation) +- Performance-critical paths use `@ccall` to libc (`memset`, `memcmp`, `memchr`, `memrchr`) with `GC.@preserve` +- Version-conditional code for Julia 1.12+ vs 1.13+ (e.g., `Base.memoryindex` for `parentindices`) +- Trait-based dispatch pattern: define `foo(x)` → `foo(MemoryKind(typeof(x)), x)` → specialized on `IsMemory`/`NotMemory` +- `@boundscheck`/`@inbounds` used throughout for safe-by-default with opt-in elision diff --git a/CLAUDE.md b/CLAUDE.md index cc6c085..43c994c 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,41 +1 @@ -# CLAUDE.md - -This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. - -## Project Overview - -MemoryViews.jl provides `MemoryView`, a low-level view into `Memory{T}` for Julia ≥ 1.11. It's a `DenseVector{T}` subtype representing a `MemoryRef{T}` + length, with static mutability tracking via type parameter (`Mutable`/`Immutable`). The package also defines the `MemoryKind` trait for dispatch on memory-backed types. - -## Commands - -```bash -# Run tests -JULIA_TEST_FAILFAST=true julia --project -e 'using Pkg; Pkg.test()' - -# Format code -runic -i . -``` - -## Architecture - -**Core types** (defined in `src/MemoryViews.jl`): -- `MemoryView{T, M}` where `M ∈ {Mutable, Immutable}` — the main type -- `MemoryKind` trait: `IsMemory{T}` / `NotMemory` for dispatch - -**Source files**: -- `construction.jl` — constructors from Array, Memory, String, SubArray, CodeUnits -- `basic.jl` — indexing, slicing (returns views, not copies), copying, find operations with memchr/memrchr C calls, comparison via memcmp -- `experimental.jl` — `split_first`, `split_last`, `split_at`, `split_unaligned` -- `delimited.jl` — `split_each` delimiter iterator -- `base_arrays.jl` — Vector/Memory conversion, append -- `io.jl` — `readbytes!` - -**Extensions** (`ext/`): StringViews, FixedSizeArrays, LibDeflate integration. - -## Key Patterns - -- Slicing creates views into the same memory (no allocation) -- Performance-critical paths use `@ccall` to libc (`memset`, `memcmp`, `memchr`, `memrchr`) with `GC.@preserve` -- Version-conditional code for Julia 1.12+ vs 1.13+ (e.g., `Base.memoryindex` for `parentindices`) -- Trait-based dispatch pattern: define `foo(x)` → `foo(MemoryKind(typeof(x)), x)` → specialized on `IsMemory`/`NotMemory` -- `@boundscheck`/`@inbounds` used throughout for safe-by-default with opt-in elision +@AGENTS.md diff --git a/src/io.jl b/src/io.jl index 623c4cb..5386d0b 100644 --- a/src/io.jl +++ b/src/io.jl @@ -4,6 +4,7 @@ # This encourages implementors of IOs to implement a method for this. function Base.readbytes!(io::IO, v::MutableMemoryView{UInt8}, nb::Integer = length(v)) nb = Int(nb)::Int + nb < 0 && throw(ArgumentError("Cannot read negative amount of bytes")) # A view of all the bytes not yet read remaining = @inbounds v[1:min(nb, length(v))] while !isempty(remaining) diff --git a/test/runtests.jl b/test/runtests.jl index 870422f..26727eb 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -668,6 +668,9 @@ end v = fill(0xaa, 8) readbytes!(buf, MemoryView(v), 10) @test v == b"Hello, w" + + # Negative nb is invalid + @test_throws ArgumentError readbytes!(IOBuffer(data), MemoryView(v), -1) end @testset "Base arrays" begin