Skip to content

Commit 5e4ff5a

Browse files
committed
Rename project to Sloth
1 parent d5ff082 commit 5e4ff5a

43 files changed

Lines changed: 223 additions & 218 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

AGENT_ARCH.md

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
# LazyValGrade Agent Architecture: A Catalog of Weird Shit
1+
# Sloth Agent Architecture: A Catalog of Weird Shit
22

3-
This document describes the non-obvious design decisions, workarounds, and architectural quirks in the LazyValGrade java agent. If you're reading this, you're either maintaining this codebase or morbidly curious about what it takes to rewrite bytecode at class-load time inside a running JVM.
3+
This document describes the non-obvious design decisions, workarounds, and architectural quirks in the Sloth java agent. If you're reading this, you're either maintaining this codebase or morbidly curious about what it takes to rewrite bytecode at class-load time inside a running JVM.
44

55
## Table of Contents
66

@@ -34,7 +34,7 @@ The agent's own dependencies -- including the Scala standard library -- are comp
3434

3535
The pipeline:
3636
1. Build the CLI jar (which contains the patcher)
37-
2. Run the CLI patcher against each dependency JAR: `java -cp <cliJar + deps> lazyvalgrade.cli.Main <depJar>`
37+
2. Run the CLI patcher against each dependency JAR: `java -cp <cliJar + deps> sloth.cli.Main <depJar>`
3838
3. Swap the patched JARs into the agent's assembly classpath
3939

4040
This is the `processDeps` task. The agent literally patches itself with itself.
@@ -47,9 +47,9 @@ The `DEBUG_AGENT_ASSEMBLY` environment variable enables verbose logging of this
4747

4848
## 2. Anti-Shading String Construction
4949

50-
**Files:** `LazyValGradeTransformer.scala` (line 28), `BytecodePatcher.scala` (lines 50-69)
50+
**Files:** `SlothTransformer.scala` (line 28), `BytecodePatcher.scala` (lines 50-69)
5151

52-
sbt-assembly shade rules rewrite all `scala.**` references to `lazyvalgrade.shaded.scala.**`. But the patcher needs to match and generate references to the *application's* unshaded `scala.runtime.LazyVals$`, not the agent's own shaded copy.
52+
sbt-assembly shade rules rewrite all `scala.**` references to `sloth.shaded.scala.**`. But the patcher needs to match and generate references to the *application's* unshaded `scala.runtime.LazyVals$`, not the agent's own shaded copy.
5353

5454
Two different workarounds are used, for two different reasons:
5555

@@ -89,7 +89,7 @@ The `isAssignableFrom` method reimplements the full interface-and-superclass che
8989

9090
## 4. Companion Pair Buffering
9191

92-
**File:** `LazyValGradeTransformer.scala` (lines 33, 54-58, 126-136)
92+
**File:** `SlothTransformer.scala` (lines 33, 54-58, 126-136)
9393

9494
Companion pairs (`Foo$` and `Foo`) must be patched together because OFFSET fields can live in the companion class while the `lzyINIT` methods live in the companion object. But the JVM's `ClassFileTransformer` only provides one class at a time.
9595

@@ -109,7 +109,7 @@ The solution uses a `ConcurrentHashMap` as a cross-load buffer:
109109

110110
## 5. The Never-Throw Rule (With One Exception)
111111

112-
**File:** `LazyValGradeTransformer.scala` (lines 151-164)
112+
**File:** `SlothTransformer.scala` (lines 151-164)
113113

114114
A `ClassFileTransformer` must never throw -- returning `null` means "leave unchanged." The catch block catches `Throwable` and returns `null`.
115115

@@ -119,15 +119,15 @@ A `ClassFileTransformer` must never throw -- returning `null` means "leave uncha
119119

120120
## 6. Double-Attach Guard
121121

122-
**File:** `LazyValGradeAgent.scala` (lines 20-29)
122+
**File:** `SlothAgent.scala` (lines 20-29)
123123

124124
An `AtomicInteger` detects if `premain` is called more than once, which happens if `-javaagent` is specified alongside `JAVA_TOOL_OPTIONS` containing the same agent. On the second call, it throws a `RuntimeException` to prevent duplicate transformers that would double-patch classfiles. The error message specifically tells the user to remove one of the two agent specifications.
125125

126126
---
127127

128128
## 7. Fast-Path Field Scanning
129129

130-
**File:** `LazyValGradeTransformer.scala` (lines 183-212)
130+
**File:** `SlothTransformer.scala` (lines 183-212)
131131

132132
Before doing any real work, the transformer runs a lightweight ASM pass with `SKIP_CODE | SKIP_DEBUG` that only visits field declarations. It short-circuits as soon as it finds either:
133133
- A `$lzy` field (but NOT `$lzyHandle` -- that indicates 3.8+ format, already good)
@@ -139,7 +139,7 @@ This avoids the cost of full parsing, companion loading, and group analysis for
139139

140140
## 8. getResourceAsStream: Pure I/O, Not Class Loading
141141

142-
**File:** `LazyValGradeTransformer.scala` (lines 81-100)
142+
**File:** `SlothTransformer.scala` (lines 81-100)
143143

144144
The companion's bytes are loaded via `loader.getResourceAsStream(name + ".class")` rather than `Class.forName` or `loadClass`. This is explicitly noted as "pure I/O, no class definition" because `loadClass` would:
145145
1. Trigger the transformer recursively
@@ -257,9 +257,9 @@ The agent shades *all* of its dependencies and itself:
257257
- `scribe.**` -- logging library
258258
- `perfolation.**`, `moduload.**`, `sourcecode.**` -- scribe's transitive dependencies
259259
- `com.lihaoyi.**`, `os.**`, `geny.**` -- os-lib and its dependencies
260-
- `lazyvalgrade.**` -- the entire lazyvalgrade itself
260+
- `sloth.**` -- the entire sloth itself
261261

262-
This is necessary because the agent runs in the application's JVM and cannot conflict with the application's own versions of these libraries. But it creates the anti-shading problems described in section 2, and is why the self-patching build (section 1) exists. Self-shading of the `lazyvalgrade.**` package is only really necessary for agent to be able to run in its own tests.
262+
This is necessary because the agent runs in the application's JVM and cannot conflict with the application's own versions of these libraries. But it creates the anti-shading problems described in section 2, and is why the self-patching build (section 1) exists. Self-shading of the `sloth.**` package is only really necessary for agent to be able to run in its own tests.
263263

264264
---
265265

@@ -281,9 +281,9 @@ This is why `sbt test` is slow and the CLAUDE.md instructions say to always use
281281

282282
## 18. Trace-Level Byte Dumping
283283

284-
**File:** `LazyValGradeTransformer.scala` (lines 167-180)
284+
**File:** `SlothTransformer.scala` (lines 167-180)
285285

286-
At trace log level (`-javaagent:agent.jar=trace`), the transformer dumps every patched class's bytes to `/tmp/lazyvalgrade-dump-<pid>-<name>.class`. The filename sanitizes `$` and `.` to `_`. These dumps happen even on success and are purely for post-mortem `javap` inspection.
286+
At trace log level (`-javaagent:agent.jar=trace`), the transformer dumps every patched class's bytes to `/tmp/sloth-dump-<pid>-<name>.class`. The filename sanitizes `$` and `.` to `_`. These dumps happen even on success and are purely for post-mortem `javap` inspection.
287287

288288
---
289289

CLAUDE.md

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Claude Code Development Guide
22

3-
This document contains information for Claude Code (or other AI assistants) working on the lazyvalgrade project.
3+
This document contains information for Claude Code (or other AI assistants) working on the sloth project.
44

55
## Project Structure
66

@@ -33,8 +33,8 @@ SELECT_EXAMPLE=simple-lazy-val,class-lazy-val sbt compileExamplesWithPatching
3333

3434
# Or run the assembly directly
3535
sbt testops/assembly
36-
java -jar testops/target/scala-3.3.8/lazyvalgrade-testops.jar
37-
java -jar testops/target/scala-3.3.8/lazyvalgrade-testops.jar --patch
36+
java -jar testops/target/scala-3.3.8/sloth-testops.jar
37+
java -jar testops/target/scala-3.3.8/sloth-testops.jar --patch
3838
```
3939

4040
**What it does:**
@@ -132,10 +132,10 @@ The `sbt test` invocations in the filtering examples below are illustrative —
132132

133133
```bash
134134
# Run a specific suite with filtering (preferred). Detection lives in tests-jdk9:
135-
SELECT_EXAMPLE=simple-lazy-val sbt "testsJdk9/testOnly lazyvalgrade.LazyValDetectionTests"
135+
SELECT_EXAMPLE=simple-lazy-val sbt "testsJdk9/testOnly sloth.LazyValDetectionTests"
136136

137137
# A single example's runtime behaviour (warning suite) lives in tests-jdk25:
138-
SELECT_EXAMPLE=companion-object-lazy-val sbt "testsJdk25/testOnly lazyvalgrade.BytecodePatchingTests"
138+
SELECT_EXAMPLE=companion-object-lazy-val sbt "testsJdk25/testOnly sloth.BytecodePatchingTests"
139139
```
140140

141141
### Filtering Examples and Scala Versions
@@ -152,7 +152,7 @@ SELECT_EXAMPLE=simple-lazy-val sbt test
152152
SELECT_EXAMPLE=simple-lazy-val,class-lazy-val sbt test
153153

154154
# Run specific test suite with filtering
155-
SELECT_EXAMPLE=companion-object-lazy-val sbt "testsJdk25/testOnly lazyvalgrade.BytecodePatchingTests"
155+
SELECT_EXAMPLE=companion-object-lazy-val sbt "testsJdk25/testOnly sloth.BytecodePatchingTests"
156156

157157
# Without SELECT_EXAMPLE, all examples are tested (default behavior)
158158
sbt test
@@ -168,7 +168,7 @@ ONLY_SCALA_VERSIONS=3.1.3,3.3.0 sbt test
168168
SELECT_EXAMPLE=simple-lazy-val ONLY_SCALA_VERSIONS=3.3.0,3.4.3 sbt test
169169

170170
# Test a problematic version in isolation
171-
ONLY_SCALA_VERSIONS=3.3.0 sbt "testsJdk9/testOnly lazyvalgrade.LazyValDetectionTests"
171+
ONLY_SCALA_VERSIONS=3.3.0 sbt "testsJdk9/testOnly sloth.LazyValDetectionTests"
172172
```
173173

174174
#### INSPECT_BYTECODE - Enable bytecode inspection on test failures
@@ -185,7 +185,7 @@ INSPECT_BYTECODE=true SELECT_EXAMPLE=multiple-lazy-vals ONLY_SCALA_VERSIONS=3.1.
185185

186186
# Debug a specific test with full bytecode output
187187
INSPECT_BYTECODE=1 SELECT_EXAMPLE=simple-lazy-val ONLY_SCALA_VERSIONS=3.3.0 \
188-
sbt "testsJdk9/testOnly lazyvalgrade.LazyValDetectionTests"
188+
sbt "testsJdk9/testOnly sloth.LazyValDetectionTests"
189189
```
190190

191191
**INSPECT_BYTECODE accepts:** `true`, `1`, `yes` (case insensitive)
@@ -213,11 +213,11 @@ sbt compile
213213

214214
# Build CLI assembly
215215
sbt cli/assembly
216-
# Output: cli/target/scala-3.3.8/lazyvalgrade.jar
216+
# Output: cli/target/scala-3.3.8/sloth.jar
217217

218218
# Build testops assembly
219219
sbt testops/assembly
220-
# Output: testops/target/scala-3.3.8/lazyvalgrade-testops.jar
220+
# Output: testops/target/scala-3.3.8/sloth-testops.jar
221221
```
222222

223223
## Important Notes

README.md

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ The fastest way to test whether your Scala 3 project works on JDK 26+ is the jav
8080
sbt agentInstall
8181
```
8282

83-
This places the agent JAR at `~/.lazyvalgrade/agent.jar`.
83+
This places the agent JAR at `~/.sloth/agent.jar`.
8484

8585
### Wrapper Scripts
8686

@@ -111,23 +111,23 @@ scn repl .
111111
For any JVM tool, set `JAVA_TOOL_OPTIONS` to inject the agent globally:
112112

113113
```bash
114-
export JAVA_TOOL_OPTIONS="-javaagent:$HOME/.lazyvalgrade/agent.jar"
114+
export JAVA_TOOL_OPTIONS="-javaagent:$HOME/.sloth/agent.jar"
115115
```
116116

117117
This covers any JVM process launched in that shell -- sbt, scala-cli, Mill, Gradle, plain `java`, etc. The agent automatically detects and patches Scala 3.0-3.7.x lazy val bytecode at class-load time.
118118

119119
For a single invocation without environment variables:
120120

121121
```bash
122-
java -javaagent:$HOME/.lazyvalgrade/agent.jar -jar your-app.jar
122+
java -javaagent:$HOME/.sloth/agent.jar -jar your-app.jar
123123
```
124124

125125
### Agent Options
126126

127127
Options are passed after `=` in the `-javaagent:` argument (comma-separated):
128128

129129
```bash
130-
java -javaagent:$HOME/.lazyvalgrade/agent.jar=verbose -jar your-app.jar
130+
java -javaagent:$HOME/.sloth/agent.jar=verbose -jar your-app.jar
131131
```
132132

133133
- `verbose` -- log patched classes to stderr (Debug level)
@@ -144,7 +144,7 @@ java -javaagent:$HOME/.lazyvalgrade/agent.jar=verbose -jar your-app.jar
144144
sbt cli/assembly
145145

146146
# Patch all classfiles in a directory (in-place)
147-
java -jar cli/target/scala-3.8.1/lazyvalgrade.jar <directory>
147+
java -jar cli/target/scala-3.8.1/sloth.jar <directory>
148148
```
149149

150150
The CLI recursively finds all `.class` files in the given directory, detects Scala 3.0-3.7.x lazy val implementations, and rewrites them to the 3.8+ VarHandle-based format. Use this for producing patched artifacts in build pipelines (assembly JARs, Docker images, etc.).
@@ -156,7 +156,7 @@ The CLI recursively finds all `.class` files in the given directory, detects Sca
156156
sbt agentInstall
157157

158158
# Run any JVM application with the agent
159-
java -javaagent:$HOME/.lazyvalgrade/agent.jar -jar your-app.jar
159+
java -javaagent:$HOME/.sloth/agent.jar -jar your-app.jar
160160
```
161161

162162
The agent intercepts class loading, detects Scala 3.0-3.7.x lazy val bytecode, and rewrites it to the 3.8+ format before the class is loaded. No changes to application code or build required.

SEMANTIC_COMPARISON.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,8 @@ This comparison ignores all bytecode differences except lazy val implementation
4545
## Usage
4646

4747
```scala
48-
import lazyvalgrade.classfile.{ClassfileParser, ClassInfo}
49-
import lazyvalgrade.lazyval.{SemanticLazyValComparator, LazyValFormatter}
48+
import sloth.classfile.{ClassfileParser, ClassInfo}
49+
import sloth.lazyval.{SemanticLazyValComparator, LazyValFormatter}
5050

5151
// Parse two classfiles (same source, different Scala versions)
5252
val bytes1: Array[Byte] = ??? // From Scala 3.3

agent/src/main/scala/lazyvalgrade/agent/AgentConfig.scala renamed to agent/src/main/scala/sloth/agent/AgentConfig.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
package lazyvalgrade.agent
1+
package sloth.agent
22

3-
/** Configuration for the LazyValGrade java agent.
3+
/** Configuration for the Sloth java agent.
44
*
55
* Parsed from the agent arguments string (comma-separated key=value pairs).
66
*

agent/src/main/scala/lazyvalgrade/agent/LazyValGradeAgent.scala renamed to agent/src/main/scala/sloth/agent/SlothAgent.scala

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,24 @@
1-
package lazyvalgrade.agent
1+
package sloth.agent
22

33
import java.lang.instrument.Instrumentation
44

5-
/** Java agent entry point for LazyValGrade.
5+
/** Java agent entry point for Sloth.
66
*
77
* Patches Scala 3.0-3.7 lazy val bytecode at class-load time to use
88
* VarHandle-based implementation (3.8+ format), avoiding sun.misc.Unsafe.
99
*
10-
* Usage: java -javaagent:lazyvalgrade-agent.jar[=options] -jar app.jar
10+
* Usage: java -javaagent:sloth-agent.jar[=options] -jar app.jar
1111
*
1212
* Options (comma-separated):
1313
* - verbose: Log patched classes and internals to stderr (Debug level)
1414
* - trace: Log everything including byte dumps to stderr (Trace level)
1515
* - include=com.example.: Only transform matching packages
1616
* - exclude=com.example.internal.: Skip matching packages
1717
*/
18-
object LazyValGradeAgent {
18+
object SlothAgent {
1919

2020
/** Log prefix constructed to survive sbt-assembly shade rules. */
21-
val logPrefix: String = Array("[lazy", "val", "grade]").mkString
21+
val logPrefix: String = Array("[sl", "oth]").mkString
2222

2323
private val instanceCounter = new java.util.concurrent.atomic.AtomicInteger(0)
2424

@@ -44,7 +44,7 @@ object LazyValGradeAgent {
4444
)
4545
.replace()
4646

47-
val transformer = new LazyValGradeTransformer(config)
47+
val transformer = new SlothTransformer(config)
4848
inst.addTransformer(transformer)
4949

5050
scribe.info(s"$logPrefix Agent installed")

agent/src/main/scala/lazyvalgrade/agent/LazyValGradeTransformer.scala renamed to agent/src/main/scala/sloth/agent/SlothTransformer.scala

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
1-
package lazyvalgrade.agent
1+
package sloth.agent
22

33
import java.lang.instrument.ClassFileTransformer
44
import java.security.ProtectionDomain
55
import java.util.concurrent.ConcurrentHashMap
66
import org.objectweb.asm.{ClassReader, ClassVisitor, FieldVisitor, Opcodes}
7-
import lazyvalgrade.analysis.LazyValAnalyzer
8-
import lazyvalgrade.patching.BytecodePatcher
7+
import sloth.analysis.LazyValAnalyzer
8+
import sloth.patching.BytecodePatcher
99

1010
/** ClassFileTransformer that patches Scala 3.0-3.7 lazy val bytecode at load time.
1111
*
@@ -15,7 +15,7 @@ import lazyvalgrade.patching.BytecodePatcher
1515
* (pure I/O, no class loading) and both are patched together. The companion's
1616
* patched bytes are buffered for when it actually loads.
1717
*/
18-
class LazyValGradeTransformer(config: AgentConfig) extends ClassFileTransformer {
18+
class SlothTransformer(config: AgentConfig) extends ClassFileTransformer {
1919

2020
/** Packages to always skip (JDK internals, our own code, Scala runtime).
2121
* NOTE: String literals for our own package and scala/runtime/ are constructed
@@ -24,7 +24,7 @@ class LazyValGradeTransformer(config: AgentConfig) extends ClassFileTransformer
2424
*/
2525
private val skipPrefixes = Array(
2626
"java/", "javax/", "jdk/", "sun/", "com/sun/",
27-
new StringBuilder("lazy").append("valgrade/").toString,
27+
new StringBuilder("sl").append("oth/").toString,
2828
new StringBuilder("sca").append("la/runtime/").toString
2929
)
3030

@@ -142,15 +142,15 @@ class LazyValGradeTransformer(config: AgentConfig) extends ClassFileTransformer
142142

143143
case Some(BytecodePatcher.PatchResult.Failed(error)) =>
144144
scribe.error(s"FATAL: Failed to patch $dotName:\n$error")
145-
throw new lazyvalgrade.patching.LazyValPatchingException(s"Failed to patch $dotName:\n$error")
145+
throw new sloth.patching.LazyValPatchingException(s"Failed to patch $dotName:\n$error")
146146

147147
case None =>
148148
scribe.debug(s" no groups to patch")
149149
null
150150
}
151151
}
152152
} catch {
153-
case t: lazyvalgrade.patching.LazyValPatchingException =>
153+
case t: sloth.patching.LazyValPatchingException =>
154154
// Re-throw diagnostic exceptions — the class must not load with broken bytecode
155155
throw t
156156
case t: Throwable =>
@@ -171,7 +171,7 @@ class LazyValGradeTransformer(config: AgentConfig) extends ClassFileTransformer
171171
try {
172172
val pid = ProcessHandle.current().pid()
173173
val safeName = name.replace('.', '_').replace('$', '_')
174-
val dumpPrefix = new StringBuilder("lazy").append("valgrade").toString
174+
val dumpPrefix = new StringBuilder("sl").append("oth").toString
175175
val path = java.nio.file.Paths.get(s"/tmp/$dumpPrefix-dump-$pid-$safeName.class")
176176
java.nio.file.Files.write(path, bytes)
177177
scribe.trace(s" dumped ${bytes.length} bytes to $path")

0 commit comments

Comments
 (0)