Skip to main content

rajac_bytecode/classfile/
mod.rs

1mod attributes;
2mod builder;
3mod descriptor;
4mod generation_context;
5mod member_lowering;
6mod naming;
7
8use rajac_ast::{Ast, AstArena, ClassDeclId};
9use rajac_base::result::RajacResult;
10use rajac_symbols::SymbolTable;
11use ristretto_classfile::ClassFile;
12
13pub use generation_context::GeneratedClassFiles;
14
15use builder::{classfile_from_class_decl_with_context, emit_classfiles_for_class};
16use generation_context::ClassfileGenerationContext;
17use naming::internal_class_name;
18
19pub(crate) use attributes::{
20    build_inner_classes_attribute, class_access_flags, exceptions_attribute_from_ast_types,
21    has_modifier,
22};
23pub(crate) use descriptor::{
24    constructor_to_descriptor, method_to_descriptor, type_to_descriptor,
25    type_to_internal_class_name,
26};
27pub(crate) use generation_context::NestedClassInfo;
28pub(crate) use member_lowering::{
29    constructor_from_ast, enum_constructor_from_ast, field_from_ast, method_from_ast,
30};
31pub(crate) use naming::collect_nested_class_infos;
32
33pub fn generate_classfiles(
34    ast: &Ast,
35    arena: &AstArena,
36    type_arena: &rajac_types::TypeArena,
37    symbol_table: &SymbolTable,
38) -> RajacResult<Vec<ClassFile>> {
39    Ok(generate_classfiles_with_report(ast, arena, type_arena, symbol_table)?.class_files)
40}
41
42pub fn generate_classfiles_with_report(
43    ast: &Ast,
44    arena: &AstArena,
45    type_arena: &rajac_types::TypeArena,
46    symbol_table: &SymbolTable,
47) -> RajacResult<GeneratedClassFiles> {
48    let mut class_files = Vec::new();
49    let mut unsupported_features = Vec::new();
50    let mut generation_context = ClassfileGenerationContext {
51        type_arena,
52        symbol_table,
53        unsupported_features: &mut unsupported_features,
54    };
55    for class_id in &ast.classes {
56        let class = arena.class_decl(*class_id);
57        let internal_name = internal_class_name(ast, &class.name, symbol_table);
58        emit_classfiles_for_class(
59            arena,
60            *class_id,
61            internal_name.into(),
62            None,
63            &mut class_files,
64            &mut generation_context,
65        )?;
66    }
67    Ok(GeneratedClassFiles {
68        class_files,
69        unsupported_features,
70    })
71}
72
73pub fn classfile_from_class_decl(
74    ast: &Ast,
75    arena: &AstArena,
76    class_id: ClassDeclId,
77    type_arena: &rajac_types::TypeArena,
78    symbol_table: &SymbolTable,
79) -> RajacResult<ClassFile> {
80    let class = arena.class_decl(class_id);
81    let this_internal_name = internal_class_name(ast, &class.name, symbol_table);
82    let mut unsupported_features = Vec::new();
83    let mut generation_context = ClassfileGenerationContext {
84        type_arena,
85        symbol_table,
86        unsupported_features: &mut unsupported_features,
87    };
88    classfile_from_class_decl_with_context(
89        arena,
90        class_id,
91        &this_internal_name,
92        None,
93        &[],
94        &mut generation_context,
95    )
96}
97
98#[cfg(test)]
99mod tests {
100    use super::*;
101    use rajac_ast::{
102        Ast, AstArena, AstType, ClassDecl, ClassKind, ClassMember, Constructor as AstConstructor,
103        EnumEntry, Method, Modifiers, PackageDecl, Param, PrimitiveType, QualifiedName,
104    };
105    use rajac_base::shared_string::SharedString;
106    use rajac_types::Ident;
107    use ristretto_classfile::attributes::{Attribute, NestedClassAccessFlags};
108
109    #[test]
110    fn generates_minimal_abstract_method_without_code_attribute() -> RajacResult<()> {
111        let mut arena = AstArena::new();
112        let mut ast = Ast::new(SharedString::new("test"));
113        let type_arena = rajac_types::TypeArena::new();
114        let symbol_table = SymbolTable::new();
115
116        let void_ty = arena.alloc_type(AstType::Primitive {
117            kind: PrimitiveType::Void,
118            ty: rajac_types::TypeId::INVALID,
119        });
120        let int_ty = arena.alloc_type(AstType::Primitive {
121            kind: PrimitiveType::Int,
122            ty: rajac_types::TypeId::INVALID,
123        });
124
125        let param_id = arena.alloc_param(Param {
126            ty: int_ty,
127            name: Ident::new(SharedString::new("x")),
128            varargs: false,
129        });
130
131        let method = Method {
132            name: Ident::new(SharedString::new("f")),
133            params: vec![param_id],
134            return_ty: void_ty,
135            body: None,
136            throws: vec![],
137            modifiers: Modifiers(Modifiers::PUBLIC),
138        };
139
140        let member_id = arena.alloc_class_member(ClassMember::Method(method));
141
142        let class_id = arena.alloc_class_decl(ClassDecl {
143            kind: ClassKind::Interface,
144            name: Ident::new(SharedString::new("Foo")),
145            type_params: vec![],
146            extends: None,
147            implements: vec![],
148            permits: vec![],
149            enum_entries: vec![],
150            members: vec![member_id],
151            modifiers: Modifiers(Modifiers::PUBLIC),
152        });
153
154        ast.classes.push(class_id);
155
156        let mut class_files = generate_classfiles(&ast, &arena, &type_arena, &symbol_table)?;
157        assert_eq!(class_files.len(), 1);
158
159        let class_file = class_files.pop().unwrap();
160        class_file.verify()?;
161
162        assert_eq!(class_file.methods.len(), 1);
163        assert!(class_file.methods[0].attributes.is_empty());
164
165        Ok(())
166    }
167
168    #[test]
169    fn generates_bytecode_for_methods_with_bodies() -> RajacResult<()> {
170        let mut arena = AstArena::new();
171        let mut ast = Ast::new(SharedString::new("test"));
172        let type_arena = rajac_types::TypeArena::new();
173        let symbol_table = SymbolTable::new();
174
175        let void_ty = arena.alloc_type(AstType::Primitive {
176            kind: PrimitiveType::Void,
177            ty: rajac_types::TypeId::INVALID,
178        });
179        let empty_block = arena.alloc_stmt(rajac_ast::Stmt::Block(vec![]));
180
181        let method = Method {
182            name: Ident::new(SharedString::new("g")),
183            params: vec![],
184            return_ty: void_ty,
185            body: Some(empty_block),
186            throws: vec![],
187            modifiers: Modifiers(Modifiers::PUBLIC),
188        };
189
190        let member_id = arena.alloc_class_member(ClassMember::Method(method));
191
192        let class_id = arena.alloc_class_decl(ClassDecl {
193            kind: ClassKind::Class,
194            name: Ident::new(SharedString::new("Foo")),
195            type_params: vec![],
196            extends: None,
197            implements: vec![],
198            permits: vec![],
199            enum_entries: vec![],
200            members: vec![member_id],
201            modifiers: Modifiers(Modifiers::PUBLIC),
202        });
203
204        ast.classes.push(class_id);
205
206        let mut class_files = generate_classfiles(&ast, &arena, &type_arena, &symbol_table)?;
207        assert_eq!(class_files.len(), 1);
208
209        let class_file = class_files.pop().unwrap();
210        class_file.verify()?;
211        assert_eq!(class_file.methods.len(), 2);
212
213        let method_with_body = class_file
214            .methods
215            .iter()
216            .find(|m| class_file.constant_pool.try_get_utf8(m.name_index).ok() == Some("g"))
217            .expect("method 'g' should be present");
218
219        assert!(!method_with_body.attributes.is_empty());
220        let has_code = method_with_body
221            .attributes
222            .iter()
223            .any(|attr| matches!(attr, Attribute::Code { .. }));
224        assert!(has_code);
225
226        Ok(())
227    }
228
229    #[test]
230    fn emits_inner_class_files_and_attributes() -> RajacResult<()> {
231        let mut arena = AstArena::new();
232        let mut ast = Ast::new(SharedString::new("test"));
233        let type_arena = rajac_types::TypeArena::new();
234        let symbol_table = SymbolTable::new();
235        ast.package = Some(PackageDecl {
236            name: QualifiedName::new(vec![SharedString::new("p")]),
237        });
238
239        let inner_id = arena.alloc_class_decl(ClassDecl {
240            kind: ClassKind::Class,
241            name: Ident::new(SharedString::new("Inner")),
242            type_params: vec![],
243            extends: None,
244            implements: vec![],
245            permits: vec![],
246            enum_entries: vec![],
247            members: vec![],
248            modifiers: Modifiers(Modifiers::PRIVATE),
249        });
250
251        let inner_member_id = arena.alloc_class_member(ClassMember::NestedClass(inner_id));
252
253        let outer_id = arena.alloc_class_decl(ClassDecl {
254            kind: ClassKind::Class,
255            name: Ident::new(SharedString::new("Outer")),
256            type_params: vec![],
257            extends: None,
258            implements: vec![],
259            permits: vec![],
260            enum_entries: vec![],
261            members: vec![inner_member_id],
262            modifiers: Modifiers(Modifiers::PUBLIC),
263        });
264
265        ast.classes.push(outer_id);
266
267        let class_files = generate_classfiles(&ast, &arena, &type_arena, &symbol_table)?;
268        assert_eq!(class_files.len(), 2);
269
270        let mut outer = None;
271        let mut inner = None;
272
273        for class_file in &class_files {
274            match class_file.class_name()? {
275                "p/Outer" => outer = Some(class_file),
276                "p/Outer$Inner" => inner = Some(class_file),
277                other => panic!("unexpected class emitted: {other}"),
278            }
279        }
280
281        let outer = outer.expect("outer class not emitted");
282        let inner = inner.expect("inner class not emitted");
283
284        let outer_inner_attr = outer
285            .attributes
286            .iter()
287            .find_map(|attr| match attr {
288                Attribute::InnerClasses { classes, .. } => Some(classes),
289                _ => None,
290            })
291            .expect("outer class missing InnerClasses attribute");
292
293        let outer_entry = outer_inner_attr
294            .iter()
295            .find(|entry| {
296                outer
297                    .constant_pool
298                    .try_get_class(entry.class_info_index)
299                    .ok()
300                    == Some("p/Outer$Inner")
301            })
302            .expect("outer class missing inner class entry");
303
304        assert_eq!(
305            outer
306                .constant_pool
307                .try_get_class(outer_entry.outer_class_info_index)?,
308            "p/Outer"
309        );
310        assert_eq!(
311            outer.constant_pool.try_get_utf8(outer_entry.name_index)?,
312            "Inner"
313        );
314        assert!(
315            outer_entry
316                .access_flags
317                .contains(NestedClassAccessFlags::PRIVATE)
318        );
319
320        let inner_attr = inner
321            .attributes
322            .iter()
323            .find_map(|attr| match attr {
324                Attribute::InnerClasses { classes, .. } => Some(classes),
325                _ => None,
326            })
327            .expect("inner class missing InnerClasses attribute");
328
329        let inner_entry = inner_attr
330            .iter()
331            .find(|entry| {
332                inner
333                    .constant_pool
334                    .try_get_class(entry.class_info_index)
335                    .ok()
336                    == Some("p/Outer$Inner")
337            })
338            .expect("inner class missing self entry");
339
340        assert_eq!(
341            inner
342                .constant_pool
343                .try_get_class(inner_entry.outer_class_info_index)?,
344            "p/Outer"
345        );
346        assert_eq!(
347            inner.constant_pool.try_get_utf8(inner_entry.name_index)?,
348            "Inner"
349        );
350
351        Ok(())
352    }
353
354    #[test]
355    fn emits_nested_enum_class_files() -> RajacResult<()> {
356        let mut arena = AstArena::new();
357        let mut ast = Ast::new(SharedString::new("test"));
358        let type_arena = rajac_types::TypeArena::new();
359        let symbol_table = SymbolTable::new();
360        ast.package = Some(PackageDecl {
361            name: QualifiedName::new(vec![SharedString::new("p")]),
362        });
363
364        let inner_id = arena.alloc_class_decl(ClassDecl {
365            kind: ClassKind::Enum,
366            name: Ident::new(SharedString::new("Inner")),
367            type_params: vec![],
368            extends: None,
369            implements: vec![],
370            permits: vec![],
371            enum_entries: vec![EnumEntry {
372                name: Ident::new(SharedString::new("VALUE")),
373                args: vec![],
374                body: None,
375            }],
376            members: vec![],
377            modifiers: Modifiers(Modifiers::PRIVATE),
378        });
379
380        let inner_member_id = arena.alloc_class_member(ClassMember::NestedEnum(inner_id));
381
382        let outer_id = arena.alloc_class_decl(ClassDecl {
383            kind: ClassKind::Class,
384            name: Ident::new(SharedString::new("Outer")),
385            type_params: vec![],
386            extends: None,
387            implements: vec![],
388            permits: vec![],
389            enum_entries: vec![],
390            members: vec![inner_member_id],
391            modifiers: Modifiers(Modifiers::PUBLIC),
392        });
393
394        ast.classes.push(outer_id);
395
396        let class_files = generate_classfiles(&ast, &arena, &type_arena, &symbol_table)?;
397        assert_eq!(class_files.len(), 2);
398        assert!(
399            class_files
400                .iter()
401                .any(|class_file| class_file.class_name().ok() == Some("p/Outer"))
402        );
403        assert!(
404            class_files
405                .iter()
406                .any(|class_file| class_file.class_name().ok() == Some("p/Outer$Inner"))
407        );
408
409        Ok(())
410    }
411
412    #[test]
413    fn emits_explicit_constructors_as_init_methods() -> RajacResult<()> {
414        let mut arena = AstArena::new();
415        let mut ast = Ast::new(SharedString::new("test"));
416        let type_arena = rajac_types::TypeArena::new();
417        let symbol_table = SymbolTable::new();
418
419        let int_ty = arena.alloc_type(AstType::Primitive {
420            kind: PrimitiveType::Int,
421            ty: rajac_types::TypeId::INVALID,
422        });
423        let param_id = arena.alloc_param(Param {
424            ty: int_ty,
425            name: Ident::new(SharedString::new("x")),
426            varargs: false,
427        });
428        let body = arena.alloc_stmt(rajac_ast::Stmt::Block(vec![]));
429        let constructor = AstConstructor {
430            name: Ident::new(SharedString::new("Foo")),
431            params: vec![param_id],
432            body: Some(body),
433            throws: vec![],
434            modifiers: Modifiers(Modifiers::PUBLIC),
435        };
436        let ctor_member_id = arena.alloc_class_member(ClassMember::Constructor(constructor));
437
438        let class_id = arena.alloc_class_decl(ClassDecl {
439            kind: ClassKind::Class,
440            name: Ident::new(SharedString::new("Foo")),
441            type_params: vec![],
442            extends: None,
443            implements: vec![],
444            permits: vec![],
445            enum_entries: vec![],
446            members: vec![ctor_member_id],
447            modifiers: Modifiers(Modifiers::PUBLIC),
448        });
449        ast.classes.push(class_id);
450
451        let mut class_files = generate_classfiles(&ast, &arena, &type_arena, &symbol_table)?;
452        let class_file = class_files.pop().unwrap();
453        class_file.verify()?;
454
455        assert_eq!(class_file.methods.len(), 1);
456        let constructor = &class_file.methods[0];
457        assert_eq!(
458            class_file
459                .constant_pool
460                .try_get_utf8(constructor.name_index)?,
461            "<init>"
462        );
463        assert_eq!(
464            class_file
465                .constant_pool
466                .try_get_utf8(constructor.descriptor_index)?,
467            "(I)V"
468        );
469        assert!(
470            constructor
471                .attributes
472                .iter()
473                .any(|attribute| matches!(attribute, Attribute::Code { .. }))
474        );
475
476        Ok(())
477    }
478
479    #[test]
480    fn emits_enum_fields_and_synthetic_methods() -> RajacResult<()> {
481        let mut arena = AstArena::new();
482        let mut ast = Ast::new(SharedString::new("test"));
483        let type_arena = rajac_types::TypeArena::new();
484        let symbol_table = SymbolTable::new();
485
486        let class_id = arena.alloc_class_decl(ClassDecl {
487            kind: ClassKind::Enum,
488            name: Ident::new(SharedString::new("Color")),
489            type_params: vec![],
490            extends: None,
491            implements: vec![],
492            permits: vec![],
493            enum_entries: vec![
494                EnumEntry {
495                    name: Ident::new(SharedString::new("RED")),
496                    args: vec![],
497                    body: None,
498                },
499                EnumEntry {
500                    name: Ident::new(SharedString::new("GREEN")),
501                    args: vec![],
502                    body: None,
503                },
504            ],
505            members: vec![],
506            modifiers: Modifiers(Modifiers::PUBLIC),
507        });
508        ast.classes.push(class_id);
509
510        let mut class_files = generate_classfiles(&ast, &arena, &type_arena, &symbol_table)?;
511        let class_file = class_files.pop().unwrap();
512        class_file.verify()?;
513
514        let field_names = class_file
515            .fields
516            .iter()
517            .map(|field| class_file.constant_pool.try_get_utf8(field.name_index))
518            .collect::<Result<Vec<_>, _>>()?;
519        assert!(field_names.contains(&"RED"));
520        assert!(field_names.contains(&"GREEN"));
521        assert!(field_names.contains(&"$VALUES"));
522
523        let method_names = class_file
524            .methods
525            .iter()
526            .map(|method| class_file.constant_pool.try_get_utf8(method.name_index))
527            .collect::<Result<Vec<_>, _>>()?;
528        assert!(method_names.contains(&"values"));
529        assert!(method_names.contains(&"valueOf"));
530        assert!(method_names.contains(&"<clinit>"));
531
532        Ok(())
533    }
534
535    #[test]
536    fn emits_exceptions_attribute_for_methods() -> RajacResult<()> {
537        let mut arena = AstArena::new();
538        let mut ast = Ast::new(SharedString::new("test"));
539        let mut symbol_table = SymbolTable::new();
540
541        let exception_ty_id = symbol_table.add_class(
542            "java.lang",
543            "Exception",
544            rajac_types::Type::class(
545                rajac_types::ClassType::new(SharedString::new("Exception"))
546                    .with_package(SharedString::new("java.lang")),
547            ),
548            rajac_symbols::SymbolKind::Class,
549        );
550        let type_arena = symbol_table.type_arena().clone();
551        let void_ty = arena.alloc_type(AstType::Primitive {
552            kind: PrimitiveType::Void,
553            ty: rajac_types::TypeId::INVALID,
554        });
555        let throws_ty = arena.alloc_type(AstType::Simple {
556            name: SharedString::new("Exception"),
557            type_args: vec![],
558            ty: exception_ty_id,
559        });
560        let empty_block = arena.alloc_stmt(rajac_ast::Stmt::Block(vec![]));
561
562        let method = Method {
563            name: Ident::new(SharedString::new("g")),
564            params: vec![],
565            return_ty: void_ty,
566            body: Some(empty_block),
567            throws: vec![throws_ty],
568            modifiers: Modifiers(Modifiers::PUBLIC),
569        };
570
571        let member_id = arena.alloc_class_member(ClassMember::Method(method));
572        let class_id = arena.alloc_class_decl(ClassDecl {
573            kind: ClassKind::Class,
574            name: Ident::new(SharedString::new("Foo")),
575            type_params: vec![],
576            extends: None,
577            implements: vec![],
578            permits: vec![],
579            enum_entries: vec![],
580            members: vec![member_id],
581            modifiers: Modifiers(Modifiers::PUBLIC),
582        });
583        ast.classes.push(class_id);
584
585        let mut class_files = generate_classfiles(&ast, &arena, &type_arena, &symbol_table)?;
586        let class_file = class_files.pop().unwrap();
587        class_file.verify()?;
588
589        let method = class_file
590            .methods
591            .iter()
592            .find(|method| {
593                class_file
594                    .constant_pool
595                    .try_get_utf8(method.name_index)
596                    .ok()
597                    == Some("g")
598            })
599            .expect("method 'g' should be present");
600
601        let exceptions = method
602            .attributes
603            .iter()
604            .find_map(|attribute| match attribute {
605                Attribute::Exceptions {
606                    exception_indexes, ..
607                } => Some(exception_indexes),
608                _ => None,
609            })
610            .expect("Exceptions attribute should be present");
611
612        assert_eq!(exceptions.len(), 1);
613        assert_eq!(
614            class_file.constant_pool.try_get_class(exceptions[0])?,
615            "java/lang/Exception"
616        );
617        Ok(())
618    }
619
620    #[test]
621    fn emits_exceptions_attribute_for_constructors() -> RajacResult<()> {
622        let mut arena = AstArena::new();
623        let mut ast = Ast::new(SharedString::new("test"));
624        let mut symbol_table = SymbolTable::new();
625
626        let exception_ty_id = symbol_table.add_class(
627            "java.lang",
628            "Exception",
629            rajac_types::Type::class(
630                rajac_types::ClassType::new(SharedString::new("Exception"))
631                    .with_package(SharedString::new("java.lang")),
632            ),
633            rajac_symbols::SymbolKind::Class,
634        );
635        let type_arena = symbol_table.type_arena().clone();
636        let throws_ty = arena.alloc_type(AstType::Simple {
637            name: SharedString::new("Exception"),
638            type_args: vec![],
639            ty: exception_ty_id,
640        });
641        let body = arena.alloc_stmt(rajac_ast::Stmt::Block(vec![]));
642        let constructor = AstConstructor {
643            name: Ident::new(SharedString::new("Foo")),
644            params: vec![],
645            body: Some(body),
646            throws: vec![throws_ty],
647            modifiers: Modifiers(Modifiers::PUBLIC),
648        };
649        let ctor_member_id = arena.alloc_class_member(ClassMember::Constructor(constructor));
650
651        let class_id = arena.alloc_class_decl(ClassDecl {
652            kind: ClassKind::Class,
653            name: Ident::new(SharedString::new("Foo")),
654            type_params: vec![],
655            extends: None,
656            implements: vec![],
657            permits: vec![],
658            enum_entries: vec![],
659            members: vec![ctor_member_id],
660            modifiers: Modifiers(Modifiers::PUBLIC),
661        });
662        ast.classes.push(class_id);
663
664        let mut class_files = generate_classfiles(&ast, &arena, &type_arena, &symbol_table)?;
665        let class_file = class_files.pop().unwrap();
666        class_file.verify()?;
667
668        let constructor = class_file
669            .methods
670            .iter()
671            .find(|method| {
672                class_file
673                    .constant_pool
674                    .try_get_utf8(method.name_index)
675                    .ok()
676                    == Some("<init>")
677            })
678            .expect("constructor should be present");
679
680        let exceptions = constructor
681            .attributes
682            .iter()
683            .find_map(|attribute| match attribute {
684                Attribute::Exceptions {
685                    exception_indexes, ..
686                } => Some(exception_indexes),
687                _ => None,
688            })
689            .expect("Exceptions attribute should be present");
690
691        assert_eq!(exceptions.len(), 1);
692        assert_eq!(
693            class_file.constant_pool.try_get_class(exceptions[0])?,
694            "java/lang/Exception"
695        );
696
697        Ok(())
698    }
699}