@@ -25,6 +25,21 @@ pub struct MethodBody {
2525/// Decompile one method. Returns Err only for malformed input; unsupported
2626/// constructs degrade to comments inside the statement tree.
2727#[ track_caller]
28+ /// Drop the `super(...)` prologue a record's canonical constructor carries
29+ /// in bytecode (`invokespecial java/lang/Record.<init>`). Returns true when
30+ /// a statement was removed.
31+ fn strip_record_super_call ( body : & mut Stmt ) {
32+ fn is_super_ctor ( s : & Stmt ) -> bool {
33+ matches ! ( s, Stmt :: ExprStmt ( Expr :: Method { name, is_super: true , .. } )
34+ if & * name == "<init>" )
35+ }
36+ if let Stmt :: Block ( v) = body {
37+ v. retain ( |x| !is_super_ctor ( x) ) ;
38+ } else if is_super_ctor ( body) {
39+ * body = Stmt :: Block ( Vec :: new ( ) ) ;
40+ }
41+ }
42+
2843pub fn decompile_method (
2944 pc : & PoolClass ,
3045 pool : & ClassPool ,
@@ -735,7 +750,7 @@ pub fn decompile_method(
735750 // Type inference for synthetic (no-LVT) variables, then fix the embedded
736751 // types in Local expressions.
737752 let ret_ty = desc. ret . clone ( ) ;
738- infer_var_types ( & mut vt, & mut body, & ret_ty, pc) ;
753+ infer_var_types ( & mut vt, & mut body, & ret_ty, pc, pool ) ;
739754 // After synthetic-var inference: stack-merge locals now carry their
740755 // Int type in the VarTable (PKCS12KeyStore.isPasswordless'
741756 // `return var6_581` under a boolean return).
@@ -829,6 +844,16 @@ pub fn decompile_method(
829844 let n = errors. len ( ) ;
830845 prepend_comment ( & mut body, format ! ( "$JCDC: {} block(s) failed to decompile" , n) ) ;
831846 }
847+
848+ // A record's canonical constructor must not spell `super(...)`: its
849+ // bytecode calls `Record.<init>`, but the source form may not
850+ // (JLS 8.10.4.1) — javac rejects the class with "invalid canonical
851+ // constructor in record". Only EXPLICIT canonical constructors reach
852+ // this point (the implicit one is not emitted), and the call is the
853+ // constructor prologue, i.e. a top-level statement.
854+ if pc. is_record ( ) && pc. method_name ( m_idx) == Some ( "<init>" ) {
855+ strip_record_super_call ( & mut body) ;
856+ }
832857 // Post-pipeline safety net for the matexit splices: the conversion-
833858 // time trial cannot see pathologies CREATED BY THE POST-CONVERT
834859 // PASSES (classify_loop/rotate_empty_then rotations turning a spliced
@@ -4573,11 +4598,18 @@ enum Evidence {
45734598 BoolCandidate ,
45744599}
45754600
4576- fn infer_var_types ( vt : & mut VarTable , body : & mut Stmt , ret_ty : & JavaType , pc : & PoolClass ) {
4601+ fn infer_var_types (
4602+ vt : & mut VarTable ,
4603+ body : & mut Stmt ,
4604+ ret_ty : & JavaType ,
4605+ pc : & PoolClass ,
4606+ pool : & jcdc_jvm:: ClassPool ,
4607+ ) {
45774608 let n = vt. vars . len ( ) ;
45784609 let mut ev: Vec < Vec < Evidence > > = vec ! [ Vec :: new( ) ; n] ;
45794610 let mut bool_vars: std:: collections:: HashSet < u32 > = std:: collections:: HashSet :: new ( ) ;
4580- gather_evidence ( body, & mut ev, ret_ty, & mut bool_vars) ;
4611+ let mut throw_vars: std:: collections:: HashSet < u32 > = std:: collections:: HashSet :: new ( ) ;
4612+ gather_evidence ( body, & mut ev, ret_ty, & mut bool_vars, & mut throw_vars) ;
45814613 let mut changed: Vec < Option < JavaType > > = vec ! [ None ; n] ;
45824614 for ( v, evs) in ev. iter ( ) . enumerate ( ) {
45834615 if !vt. vars [ v] . synthetic_name || vt. vars [ v] . is_param {
@@ -4616,6 +4648,7 @@ fn infer_var_types(vt: &mut VarTable, body: &mut Stmt, ret_ty: &JavaType, pc: &P
46164648 if crate :: dbg_flag!( "JCDC_DBG_MERGE" ) && vt. stack_vars . contains ( & ( v as u32 ) ) {
46174649 eprintln ! ( "INFER stackvar v={} old={:?} t={:?} wide={}" , v, old, t, vt. wide_stack_vars. contains( & ( v as u32 ) ) ) ;
46184650 }
4651+ let mut throw_lub: Option < JavaType > = None ;
46194652 if vt. wide_stack_vars . contains ( & ( v as u32 ) ) {
46204653 // Branches disagreed (or carried a generic/Object value);
46214654 // keep the wide type so every assignment type-checks —
@@ -4636,6 +4669,28 @@ fn infer_var_types(vt: &mut VarTable, body: &mut Stmt, ret_ty: &JavaType, pc: &P
46364669 matches ! ( t, JavaType :: Object ( n) if n == "java/lang/Object" )
46374670 } ;
46384671 let uniform = ev_types. windows ( 2 ) . all ( |w| w[ 0 ] == w[ 1 ] ) ;
4672+ // A pool-aware join that lands on a REAL common superclass
4673+ // (IllegalStateException + IllegalArgumentException →
4674+ // RuntimeException) is just as usable as a uniform type:
4675+ // both assignments convert to it. Without this the merged
4676+ // value stays Object, and `throw stack3;` then fails to
4677+ // compile (JetBrains @NotNull `$$$reportNull$$$0`).
4678+ let lub = {
4679+ let mut acc: Option < JavaType > = None ;
4680+ for t in & ev_types {
4681+ acc = Some ( match acc {
4682+ None => ( * t) . clone ( ) ,
4683+ Some ( a) => join_types_pool ( & a, t, pool) ,
4684+ } ) ;
4685+ }
4686+ acc
4687+ } ;
4688+ let lub_concrete = lub
4689+ . as_ref ( )
4690+ . is_some_and ( |j| !matches ! ( j, JavaType :: Object ( n) if n == "java/lang/Object" ) ) ;
4691+ if throw_vars. contains ( & ( v as u32 ) ) && lub_concrete {
4692+ throw_lub = lub. clone ( ) ;
4693+ }
46394694 // Array covariance: `return sharedTail;` merges e.g. Class[]
46404695 // and Type[] branch values; when the method return type is
46414696 // an array and every observed value is an array, the return
@@ -4645,7 +4700,14 @@ fn infer_var_types(vt: &mut VarTable, body: &mut Stmt, ret_ty: &JavaType, pc: &P
46454700 && ev_types. iter ( ) . all ( |t| matches ! ( t, JavaType :: Array ( _) ) ) ;
46464701 let concrete = !ev_types. is_empty ( )
46474702 && !ev_types. iter ( ) . any ( plain_object)
4648- && ( uniform || covariant_ret_array) ;
4703+ && ( uniform || covariant_ret_array
4704+ // A THROWN local is the exception: `throw x` needs a
4705+ // Throwable, so a pool-aware LUB (IllegalState +
4706+ // IllegalArgument → RuntimeException) is required
4707+ // where keeping Object would not compile. Other
4708+ // merged vars keep Object — narrowing them could
4709+ // change overload resolution.
4710+ || ( throw_vars. contains ( & ( v as u32 ) ) && lub_concrete) ) ;
46494711 if crate :: dbg_flag!( "JCDC_DBG_MERGE" ) {
46504712 eprintln ! ( "WIDENARROW v={} ev={:?} concrete={}" , v, ev_types, concrete) ;
46514713 }
@@ -4658,6 +4720,12 @@ fn infer_var_types(vt: &mut VarTable, body: &mut Stmt, ret_ty: &JavaType, pc: &P
46584720 // over the 0/1-carrying Int merge type.
46594721 if matches ! ( t, JavaType :: Boolean ) && matches ! ( old, JavaType :: Int ) {
46604722 JavaType :: Boolean
4723+ } else if let Some ( l) = throw_lub {
4724+ // A thrown value must be a Throwable: take the pool-aware
4725+ // LUB of its evidence (see `lub_concrete`). Kept to this
4726+ // one consumer — narrowing other merged vars could change
4727+ // which overload a call binds to.
4728+ l
46614729 } else if vt. wide_stack_vars . contains ( & ( v as u32 ) ) {
46624730 // Widened merges narrowed here take the evidence type
46634731 // directly; joining with the plain-Object placeholder
@@ -4993,6 +5061,48 @@ fn combine_types(types: &[&JavaType]) -> Option<JavaType> {
49935061 acc
49945062}
49955063
5064+ /// Superclass chain of an internal name (itself first), or empty when the
5065+ /// class is not in the pool.
5066+ fn super_chain ( pool : & jcdc_jvm:: ClassPool , name : & str ) -> Vec < String > {
5067+ let mut out = Vec :: new ( ) ;
5068+ let mut cur = name. to_string ( ) ;
5069+ for _ in 0 ..256 {
5070+ out. push ( cur. clone ( ) ) ;
5071+ let Some ( pc) = pool. get ( & cur) else { break } ;
5072+ match pc. super_name ( ) {
5073+ Some ( s) if s != cur => cur = s. to_string ( ) ,
5074+ _ => break ,
5075+ }
5076+ }
5077+ out
5078+ }
5079+
5080+ /// Nearest common superclass of two internal names (None when unrelated).
5081+ fn common_superclass ( pool : & jcdc_jvm:: ClassPool , a : & str , b : & str ) -> Option < String > {
5082+ let cb: std:: collections:: HashSet < String > = super_chain ( pool, b) . into_iter ( ) . collect ( ) ;
5083+ super_chain ( pool, a) . into_iter ( ) . find ( |n| cb. contains ( n) )
5084+ }
5085+
5086+ /// `join_types` with hierarchy access: two unrelated reference types join to
5087+ /// their nearest common SUPERCLASS instead of plain Object. Object-typed
5088+ /// merges are only usable for reads — a merged value that is thrown must be
5089+ /// a Throwable (`throw stack3;` with an Object declaration is a javac error;
5090+ /// JetBrains @NotNull instrumentation's `$$$reportNull$$$0` merges
5091+ /// IllegalStateException / IllegalArgumentException). Interfaces and absent
5092+ /// classes fall back to the plain-Object rule.
5093+ fn join_types_pool ( a : & JavaType , b : & JavaType , pool : & jcdc_jvm:: ClassPool ) -> JavaType {
5094+ if let ( JavaType :: Object ( na) , JavaType :: Object ( nb) ) = ( a, b) {
5095+ if na != "java/lang/Object" && nb != "java/lang/Object" && na != nb {
5096+ if let Some ( lub) = common_superclass ( pool, na, nb) {
5097+ if lub != "java/lang/Object" && lub != "java/lang/Throwable" {
5098+ return JavaType :: Object ( lub. into ( ) ) ;
5099+ }
5100+ }
5101+ }
5102+ }
5103+ join_types ( a, b)
5104+ }
5105+
49965106fn join_types ( a : & JavaType , b : & JavaType ) -> JavaType {
49975107 use JavaType :: * ;
49985108 if a == b {
@@ -5054,9 +5164,10 @@ fn gather_evidence(
50545164 ev : & mut Vec < Vec < Evidence > > ,
50555165 ret_ty : & JavaType ,
50565166 bool_vars : & mut std:: collections:: HashSet < u32 > ,
5167+ throw_vars : & mut std:: collections:: HashSet < u32 > ,
50575168) {
50585169 match s {
5059- Stmt :: Block ( v) => v. iter ( ) . for_each ( |x| gather_evidence ( x, ev, ret_ty, bool_vars) ) ,
5170+ Stmt :: Block ( v) => v. iter ( ) . for_each ( |x| gather_evidence ( x, ev, ret_ty, bool_vars, throw_vars ) ) ,
50605171 Stmt :: ExprStmt ( e) => gather_expr ( e, ev, None , bool_vars) ,
50615172 Stmt :: LocalDef { var, init, .. } => {
50625173 if let Some ( e) = init {
@@ -5103,6 +5214,7 @@ fn gather_evidence(
51035214 gather_expr ( e, ev, None , bool_vars) ;
51045215 // A thrown local must be a Throwable.
51055216 if let Expr :: Local { var, .. } = e {
5217+ throw_vars. insert ( * var) ;
51065218 push_ev (
51075219 ev,
51085220 * var,
@@ -5113,67 +5225,67 @@ fn gather_evidence(
51135225 Stmt :: If { cond, then_stmt, else_stmt } => {
51145226 mark_bool_cond ( cond, bool_vars) ;
51155227 gather_expr ( cond, ev, None , bool_vars) ;
5116- gather_evidence ( then_stmt, ev, ret_ty, bool_vars) ;
5228+ gather_evidence ( then_stmt, ev, ret_ty, bool_vars, throw_vars ) ;
51175229 if let Some ( x) = else_stmt {
5118- gather_evidence ( x, ev, ret_ty, bool_vars) ;
5230+ gather_evidence ( x, ev, ret_ty, bool_vars, throw_vars ) ;
51195231 }
51205232 }
51215233 Stmt :: While { cond, body } => {
51225234 mark_bool_cond ( cond, bool_vars) ;
51235235 gather_expr ( cond, ev, None , bool_vars) ;
5124- gather_evidence ( body, ev, ret_ty, bool_vars) ;
5236+ gather_evidence ( body, ev, ret_ty, bool_vars, throw_vars ) ;
51255237 }
51265238 Stmt :: DoWhile { body, cond } => {
51275239 mark_bool_cond ( cond, bool_vars) ;
51285240 gather_expr ( cond, ev, None , bool_vars) ;
5129- gather_evidence ( body, ev, ret_ty, bool_vars) ;
5241+ gather_evidence ( body, ev, ret_ty, bool_vars, throw_vars ) ;
51305242 }
51315243 Stmt :: For { init, cond, update, body } => {
5132- init. iter ( ) . for_each ( |i| gather_evidence ( i, ev, ret_ty, bool_vars) ) ;
5244+ init. iter ( ) . for_each ( |i| gather_evidence ( i, ev, ret_ty, bool_vars, throw_vars ) ) ;
51335245 if let Some ( c) = cond {
51345246 mark_bool_cond ( c, bool_vars) ;
51355247 gather_expr ( c, ev, None , bool_vars) ;
51365248 }
51375249 update. iter ( ) . for_each ( |u| gather_expr ( u, ev, None , bool_vars) ) ;
5138- gather_evidence ( body, ev, ret_ty, bool_vars) ;
5250+ gather_evidence ( body, ev, ret_ty, bool_vars, throw_vars ) ;
51395251 }
51405252 Stmt :: ForEach { iterable, body, .. } => {
51415253 gather_expr ( iterable, ev, None , bool_vars) ;
5142- gather_evidence ( body, ev, ret_ty, bool_vars) ;
5254+ gather_evidence ( body, ev, ret_ty, bool_vars, throw_vars ) ;
51435255 }
51445256 Stmt :: Switch { selector, cases, default, .. } => {
51455257 gather_expr ( selector, ev, None , bool_vars) ;
51465258 for c in cases {
5147- c. body . iter ( ) . for_each ( |st| gather_evidence ( st, ev, ret_ty, bool_vars) ) ;
5259+ c. body . iter ( ) . for_each ( |st| gather_evidence ( st, ev, ret_ty, bool_vars, throw_vars ) ) ;
51485260 }
51495261 if let Some ( d) = default {
5150- gather_evidence ( d, ev, ret_ty, bool_vars) ;
5262+ gather_evidence ( d, ev, ret_ty, bool_vars, throw_vars ) ;
51515263 }
51525264 }
51535265 Stmt :: Try { body, catches, finally } => {
5154- gather_evidence ( body, ev, ret_ty, bool_vars) ;
5266+ gather_evidence ( body, ev, ret_ty, bool_vars, throw_vars ) ;
51555267 for c in catches {
5156- gather_evidence ( & c. body , ev, ret_ty, bool_vars) ;
5268+ gather_evidence ( & c. body , ev, ret_ty, bool_vars, throw_vars ) ;
51575269 }
51585270 if let Some ( f) = finally {
5159- gather_evidence ( f, ev, ret_ty, bool_vars) ;
5271+ gather_evidence ( f, ev, ret_ty, bool_vars, throw_vars ) ;
51605272 }
51615273 }
51625274 Stmt :: TryWithResources { resources, body, catches, finally } => {
5163- for res in resources. iter ( ) { gather_evidence ( res, ev, ret_ty, bool_vars) ; }
5164- gather_evidence ( body, ev, ret_ty, bool_vars) ;
5275+ for res in resources. iter ( ) { gather_evidence ( res, ev, ret_ty, bool_vars, throw_vars ) ; }
5276+ gather_evidence ( body, ev, ret_ty, bool_vars, throw_vars ) ;
51655277 for c in catches {
5166- gather_evidence ( & c. body , ev, ret_ty, bool_vars) ;
5278+ gather_evidence ( & c. body , ev, ret_ty, bool_vars, throw_vars ) ;
51675279 }
51685280 if let Some ( f) = finally {
5169- gather_evidence ( f, ev, ret_ty, bool_vars) ;
5281+ gather_evidence ( f, ev, ret_ty, bool_vars, throw_vars ) ;
51705282 }
51715283 }
51725284 Stmt :: Synchronized { lock, body } => {
51735285 gather_expr ( lock, ev, None , bool_vars) ;
5174- gather_evidence ( body, ev, ret_ty, bool_vars) ;
5286+ gather_evidence ( body, ev, ret_ty, bool_vars, throw_vars ) ;
51755287 }
5176- Stmt :: Labeled { body, .. } => gather_evidence ( body, ev, ret_ty, bool_vars) ,
5288+ Stmt :: Labeled { body, .. } => gather_evidence ( body, ev, ret_ty, bool_vars, throw_vars ) ,
51775289 _ => { }
51785290 }
51795291}
0 commit comments