Repository navigation
Code Analysis Java Static Imports
A static field or method import does not automatically hide an unrelated type with the same name. Code Analysis previously treated every single static import as a possible member-type import and suppressed type lookup. A compiler-verified authored case exposes two missing file dependencies; Draft #1231 corrects this bounded case. It is open for review, not merged into Labs.
The seven-file reproduction contains package-local classes p.Target and p.Other, a separate constants.Values class with a static field named Target and a static method named Other, and this caller:
package p;
import static constants.Values.Target;
import static constants.Values.Other;
public class Use {
Target field;
Other second;
int value = Target;
void run() { Other(); }
}JDK 17's JavacTask parses and analyzes all seven sources with zero errors. Trees.getElement attributes the type uses on lines 5 and 6 to p/Target.java and p/Other.java. A no-import control selects the same files; a genuine static nested-type import selects constants/Types.java. The field and method imports still refer to constants/Values.java. These are source declaration/file expectations, not runtime call-graph evidence. JLS type-name meaning · Single static imports
- Base: Labs
bf0a29e7664f52973d06a8e6a608df2db1ecbd39, Java adapter revision 1. - Candidate:
6c82e0d58a9e26a8781e5b87fb55be3e98bbff80, Java adapter revision 3. - This is independent of Unicode Draft #1218, whose Java revision is 2. Its parsing correction is not included here.
The candidate reuses the existing index, owner lookup and inherited-member scope. It permits ordinary type lookup only when there is one analyzed owner declaring the static member, no matching direct member type, and a complete known ancestor scope without that type name. Genuine member-type imports and uncertain, duplicate or incomplete owners keep their conservative guards. The change adds no classpath or general hierarchy resolution.
| Fixed scope | Files | Import/type-reference records | Result against Labs |
|---|---|---|---|
| Authored JDK reproduction | 7 | 8 | Exactly two missing type targets restored; other records preserved |
Guava JRE f06690fa3e874f65515e8fd338a74d636e2c792f, guava/src
|
615 | 16,421 | Every record preserved; seven existing parse errors remain |
SLF4J 9783320b6c4411a0b120cad9e32cd9a981b1dac2, whole repository |
260 | 2,436 | Every record preserved; one existing source-template parse error remains |
The Guava audit found no occurrence of this trigger. The two real repositories are preservation controls, not demonstrated beneficiaries of the correction. All three scopes preserve their file, function, symbol, call and parse-error records. Normal source CLI and unpacked archive raw facts match byte for byte.
All 230 module tests pass with Node 22.23.2 and Python 3.13.16. The focused checks cover 34 Java/revision tests and 49 affected callers. The new positive test fails on exact Labs; the conservative-boundary test already passes there. JDK 17 also analyzes the eight-file positive regression without errors. An independent agent review found no actionable issue and verified interface constants, a complete ancestor chain and an inherited member-type guard with three additional JDK-backed probes. This is not human acceptance or a general accuracy measurement.
The archive was built twice identically: SHA256 d806b792ca954060d52b2abc1375de882cc55d8c060f6a58f61196a06237cac5. No live installation was changed. Existing Guava parsing gaps belong to the separately recorded Unicode correction; SLF4J's migration template is still not valid Java input. General inherited/classpath resolution and runtime dispatch remain outside this correction.
The script below requires Git, Node 22, npm, Python 3 and JDK 17 or newer. It fetches fixed public sources and installs locked analyzer dependencies only in temporary copies. JAVA_BIN can select an existing JDK executable. It checks five compiler-attributed type references, both restored records, real-project preservation controls and source/archive byte parity. It does not build or execute Guava or SLF4J applications.
A fresh public fetch passed this script with Node 22.23.2 and JDK 17.0.21. Candidate CI completed with the known README Classic digest failure, matching the exact Labs expected/actual hashes. The initial browser attempt also failed a short-wide Sequence fit check: unchanged relevant renderer/test sources passed on both exact revisions locally on macOS, and the targeted CI browser rerun passed. That initial failure is retained; the latest attempt has no other executed job failure apart from the README regression and its aggregate Node22 result. CI is not green.
Run the pinned compiler, CLI and archive comparison
#!/usr/bin/env bash
set -euo pipefail
# Requires git, Node, npm, unzip and JDK 17+. No fixture applications are run.
ARCHIFY_REF=${ARCHIFY_REF:-6c82e0d58a9e26a8781e5b87fb55be3e98bbff80}
ARCHIFY_ZIP_SHA256=${ARCHIFY_ZIP_SHA256:-d806b792ca954060d52b2abc1375de882cc55d8c060f6a58f61196a06237cac5}
[[ "$ARCHIFY_REF" =~ ^[0-9a-f]{40}$ ]] || { echo 'ARCHIFY_REF must be a full lowercase commit SHA' >&2; exit 1; }
[[ "$ARCHIFY_ZIP_SHA256" =~ ^[0-9a-f]{64}$ ]] || { echo 'ARCHIFY_ZIP_SHA256 must be a lowercase SHA256' >&2; exit 1; }
export JAVA_BIN=${JAVA_BIN:-java}
node <<'JS'
const {spawnSync}=require('node:child_process');
const r=spawnSync(process.env.JAVA_BIN,['-version'],{encoding:'utf8'});
const version=(r.stderr+r.stdout).match(/version "(\d+)(?:\.|")/);
if(r.status!==0 || !version || Number(version[1])<17) throw Error('JDK 17+ required: '+r.stderr);
console.log('Java version:',version[1]);
JS
WORK=$(mktemp -d "${TMPDIR:-/tmp}/archify-java-public.XXXXXXXX")
echo "Results and checkouts: $WORK"
export EVIDENCE_ROOT="$WORK/results" CASES="$WORK/cases.json"
mkdir -p "$EVIDENCE_ROOT" "$WORK/sources" "$WORK/classes"
mkdir -p "$WORK/sources/constants"
cat > "$WORK/sources/constants/Types.java" <<'ARCHIFY_SOURCE'
package constants;
public class Types { public static class Target {} }
ARCHIFY_SOURCE
mkdir -p "$WORK/sources/constants"
cat > "$WORK/sources/constants/Values.java" <<'ARCHIFY_SOURCE'
package constants;
public class Values { public static int Target = 7; public static void Other() {} }
ARCHIFY_SOURCE
mkdir -p "$WORK/sources/p"
cat > "$WORK/sources/p/Control.java" <<'ARCHIFY_SOURCE'
package p;
public class Control { Target field; Other second; }
ARCHIFY_SOURCE
mkdir -p "$WORK/sources/p"
cat > "$WORK/sources/p/Other.java" <<'ARCHIFY_SOURCE'
package p;
public class Other {}
ARCHIFY_SOURCE
mkdir -p "$WORK/sources/p"
cat > "$WORK/sources/p/Target.java" <<'ARCHIFY_SOURCE'
package p;
public class Target {}
ARCHIFY_SOURCE
mkdir -p "$WORK/sources/p"
cat > "$WORK/sources/p/TypeControl.java" <<'ARCHIFY_SOURCE'
package p;
import static constants.Types.Target;
public class TypeControl { Target field; }
ARCHIFY_SOURCE
mkdir -p "$WORK/sources/p"
cat > "$WORK/sources/p/Use.java" <<'ARCHIFY_SOURCE'
package p;
import static constants.Values.Target;
import static constants.Values.Other;
public class Use {
Target field;
Other second;
int value = Target;
void run() { Other(); }
}
ARCHIFY_SOURCE
cat > "$WORK/Oracle.java" <<'ARCHIFY_ORACLE'
import java.nio.file.*;
import java.util.*;
import javax.tools.*;
import com.sun.source.util.*;
import com.sun.source.tree.*;
public class Oracle {
public static void main(String[] args) throws Exception {
Path root = Path.of(args[0]);
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
DiagnosticCollector<JavaFileObject> diagnostics = new DiagnosticCollector<>();
try (StandardJavaFileManager fm = compiler.getStandardFileManager(diagnostics,null,null)) {
var files = Files.walk(root).filter(p -> p.toString().endsWith(".java")).sorted().toList();
JavacTask task = (JavacTask)compiler.getTask(null,fm,diagnostics,List.of("-proc:none","-d",args[1]),null,fm.getJavaFileObjectsFromPaths(files));
var units = new ArrayList<CompilationUnitTree>(); task.parse().forEach(units::add); task.analyze();
for (var d : diagnostics.getDiagnostics()) if (d.getKind()==Diagnostic.Kind.ERROR) throw new AssertionError(d);
Trees trees=Trees.instance(task);
for (var unit:units) new TreePathScanner<Void,Void>() {
public Void visitVariable(VariableTree node,Void unused) {
if (node.getName().toString().equals("field") || node.getName().toString().equals("second")) {
var type=trees.getTypeMirror(new TreePath(getCurrentPath(),node.getType()));
long pos=trees.getSourcePositions().getStartPosition(unit,node.getType());
var element=task.getTypes().asElement(type);
var declaration=trees.getPath(element);
System.out.println(root.relativize(Path.of(unit.getSourceFile().toUri()))+"|"+unit.getLineMap().getLineNumber(pos)+"|"+node.getType()+"|"+type+"|"+root.relativize(Path.of(declaration.getCompilationUnit().getSourceFile().toUri())));
}
return super.visitVariable(node,unused);
}
}.scan(unit,null);
System.out.println("errors=0; files="+files.size());
}
}
}
ARCHIFY_ORACLE
cat > "$WORK/public-verify.mjs" <<'ARCHIFY_VERIFY'
import fs from 'node:fs';
import path from 'node:path';
import assert from 'node:assert/strict';
import {spawnSync} from 'node:child_process';
import {createHash} from 'node:crypto';
const e=process.env.EVIDENCE_ROOT;
const label=process.argv[2]||'parent';
const cli=process.argv[3];
const cases=JSON.parse(fs.readFileSync(process.env.CASES));
const run=(command,args)=>{const r=spawnSync(command,args,{encoding:'utf8',timeout:240000,maxBuffer:2*1024*1024});assert.equal(r.status,0,r.stderr||r.stdout||String(r.error));return r.stdout;};
const hash=x=>createHash('sha256').update(x).digest('hex');
const result=[];
for(const c of cases){
if(c.pin){assert.equal(run('git',['-C',c.root,'rev-parse','HEAD']).trim(),c.pin);assert.equal(run('git',['-C',c.root,'status','--porcelain','--untracked-files=no']),'');}
const out=e+'/'+c.name+'-'+label;
fs.writeFileSync(out+'.receipt.json',run(process.execPath,[cli,'code-analysis','check',c.root,'--language','java','--fail-on','never','--out',out,'--json']));
const raw=fs.readFileSync(out+'/raw-facts.json'),facts=JSON.parse(raw);
assert.equal(facts.repository.adapterRevision,label==='parent'?1:3);
assert.equal(facts.parse_errors?.length||0,c.expectedParseErrors);
if(c.expectedImports !== undefined) assert.equal(facts.imports.length,c.expectedImports);
if(c.name==='jdk-namespaces'){
for(const name of ['Target','Other']) {
const control=facts.imports.find(x=>x.from==='p/Control.java'&&x.specifier===name);
assert.equal(control?.to,'p/'+name+'.java');
}
assert.equal(facts.imports.find(x=>x.from==='p/TypeControl.java'&&x.specifier==='constants.Types.Target')?.to,'constants/Types.java');
assert.equal(facts.imports.find(x=>x.from==='p/TypeControl.java'&&x.specifier==='Target')?.resolved,false);
for(const [name,line,to]of [['Target',5,'p/Target.java'],['Other',6,'p/Other.java']]){
const edge=facts.imports.find(x=>x.from==='p/Use.java'&&x.line===line&&x.specifier===name);
assert.ok(edge);assert.equal(edge.resolved,label!=='parent');assert.equal(edge.to,label==='parent'?undefined:to);
}
}
let changed=0;
if(label!=='parent'){
const base=JSON.parse(fs.readFileSync(e+'/'+c.name+'-parent/raw-facts.json'));
for(const k of ['files','symbols','functions','parse_errors','calls'])assert.deepEqual(facts[k],base[k],c.name+': '+k);
if(c.name==='jdk-namespaces'){
const expected=base.imports.map(x=>x.from==='p/Use.java'&&['Target','Other'].includes(x.specifier)?{...x,resolved:true,to:'p/'+x.specifier+'.java'}:x);
assert.deepEqual(facts.imports,expected);changed=2;
}else {assert.deepEqual(facts.imports,base.imports);assert.deepEqual(facts.unresolved,base.unresolved);}
}
if(label==='archive')assert.equal(hash(raw),hash(fs.readFileSync(e+'/'+c.name+'-candidate/raw-facts.json')));
result.push({name:c.name,pin:c.pin,files:facts.files.length,imports:facts.imports.length,parseErrors:facts.parse_errors?.length||0,changedRecords:changed,sha256:hash(raw),archiveParity:label==='archive'});
console.log(JSON.stringify(result.at(-1)));
}
fs.writeFileSync(e+'/'+label+'-results.json',JSON.stringify({node:process.version,result},null,2)+'\n');
ARCHIFY_VERIFY
# Source-file mode executes only the authored attribution harness. Annotation processing is disabled.
"$JAVA_BIN" "$WORK/Oracle.java" "$WORK/sources" "$WORK/classes" > "$WORK/oracle.tsv"
cat "$WORK/oracle.tsv"
node - "$WORK/oracle.tsv" <<'JS'
const fs=require('node:fs'), assert=require('node:assert/strict');
const rows=fs.readFileSync(process.argv[2],'utf8').trim().split(/\r?\n/).sort();
assert.deepEqual(rows,[
'p/Control.java|2|Target|p.Target|p/Target.java',
'p/Control.java|2|Other|p.Other|p/Other.java',
'p/TypeControl.java|3|Target|constants.Types.Target|constants/Types.java',
'p/Use.java|5|Target|p.Target|p/Target.java',
'p/Use.java|6|Other|p.Other|p/Other.java',
'errors=0; files=7'
].sort());
JS
fetch_checkout() {
local url=$1 revision=$2 dest=$3 pattern=$4
git init -q "$dest"
git -C "$dest" remote add origin "$url"
git -C "$dest" config core.sparseCheckout true
printf '%s\n' "$pattern" > "$dest/.git/info/sparse-checkout"
git -C "$dest" -c http.version=HTTP/1.1 fetch --quiet --depth=1 --filter=blob:none origin "$revision"
git -C "$dest" checkout -q --detach FETCH_HEAD
test "$(git -C "$dest" rev-parse HEAD)" = "$revision"
}
fetch_checkout https://github.com/tt-a1i/archify.git bf0a29e7664f52973d06a8e6a608df2db1ecbd39 "$WORK/parent" '/archify/'
fetch_checkout https://github.com/tt-a1i/archify.git "$ARCHIFY_REF" "$WORK/candidate" $'/archify/\n/archify.zip'
node - "$WORK/candidate/archify.zip" "$ARCHIFY_ZIP_SHA256" <<'JS'
const fs=require('node:fs'), crypto=require('node:crypto'), assert=require('node:assert/strict');
const actual=crypto.createHash('sha256').update(fs.readFileSync(process.argv[2])).digest('hex');
assert.equal(actual,process.argv[3]); console.log('archive SHA256:',actual);
JS
mkdir "$WORK/archive"
unzip -q "$WORK/candidate/archify.zip" -d "$WORK/archive"
for dir in "$WORK/parent/archify/modules/code-analysis" "$WORK/candidate/archify/modules/code-analysis" "$WORK/archive/archify/modules/code-analysis"; do
(cd "$dir" && npm ci --ignore-scripts --no-audit --no-fund)
done
fetch_checkout https://github.com/google/guava.git f06690fa3e874f65515e8fd338a74d636e2c792f "$WORK/guava" '/guava/src/'
fetch_checkout https://github.com/qos-ch/slf4j.git 9783320b6c4411a0b120cad9e32cd9a981b1dac2 "$WORK/slf4j" '/*'
node - "$WORK" <<'JS'
const fs=require('node:fs'), path=require('node:path'); const w=process.argv[2];
fs.writeFileSync(process.env.CASES,JSON.stringify([
{name:'jdk-namespaces',root:path.join(w,'sources'),expectedParseErrors:0,expectedImports:8},
{name:'guava-jre',root:path.join(w,'guava/guava/src'),pin:'f06690fa3e874f65515e8fd338a74d636e2c792f',expectedParseErrors:7,expectedImports:16421},
{name:'slf4j',root:path.join(w,'slf4j'),pin:'9783320b6c4411a0b120cad9e32cd9a981b1dac2',expectedParseErrors:1,expectedImports:2436}
]));
JS
node "$WORK/public-verify.mjs" parent "$WORK/parent/archify/bin/archify.mjs"
node "$WORK/public-verify.mjs" candidate "$WORK/candidate/archify/bin/archify.mjs"
node "$WORK/public-verify.mjs" archive "$WORK/archive/archify/bin/archify.mjs"
echo "Verified; results retained at $WORK"Validation index · Java source-set and service-selection controls