@@ -64,6 +64,24 @@ static string FirstAttributeStringArg (MetadataReader reader, CustomAttributeHan
6464 return args . Count > 0 ? args [ 0 ] : null ;
6565 }
6666
67+ static IReadOnlyList < string > AttributeStringArrayArg ( MetadataReader reader , CustomAttributeHandleCollection attributes , EntityHandle ctor )
68+ {
69+ foreach ( CustomAttributeHandle handle in attributes ) {
70+ CustomAttribute attribute = reader . GetCustomAttribute ( handle ) ;
71+ if ( attribute . Constructor != ctor ) {
72+ continue ;
73+ }
74+
75+ var decoded = attribute . DecodeValue ( Xamarin . Android . Tasks . DummyCustomAttributeProvider . Instance ) ;
76+ var result = new List < string > ( ) ;
77+ foreach ( var element in ( ImmutableArray < CustomAttributeTypedArgument < object > > ) decoded . FixedArguments [ 0 ] . Value ) {
78+ result . Add ( ( string ) element . Value ) ;
79+ }
80+ return result ;
81+ }
82+ return [ ] ;
83+ }
84+
6785 static List < KeyValuePair < int , string > > LoadedStrings ( PEReader peReader , MetadataReader reader , MethodDefinitionHandle method )
6886 {
6987 var result = new List < KeyValuePair < int , string > > ( ) ;
@@ -86,10 +104,13 @@ static List<KeyValuePair<int, string>> LoadedStrings (PEReader peReader, Metadat
86104 return result ;
87105 }
88106
89- static void AssertTableRowCountsMatch ( MetadataReader expected , MetadataReader actual )
107+ static void AssertTableRowCountsMatch ( MetadataReader expected , MetadataReader actual , params TableIndex [ ] except )
90108 {
91109 for ( int i = 0 ; i < MetadataTokens . TableCount ; i ++ ) {
92110 var table = ( TableIndex ) i ;
111+ if ( Array . IndexOf ( except , table ) >= 0 ) {
112+ continue ;
113+ }
93114 Assert . AreEqual ( expected . GetTableRowCount ( table ) , actual . GetTableRowCount ( table ) , $ "Row count of table '{ table } ' changed.") ;
94115 }
95116 }
@@ -389,6 +410,194 @@ public void RewritesAttributesAndLoadedStrings ()
389410 CollectionAssert . AreEqual ( new [ ] { "b:()V:n_Run" } , ValuesOf ( LoadedStrings ( peReader , reader , run ) ) ) ;
390411 }
391412
413+ [ Test ]
414+ public void RewritesTrimmableTypeMapKeysAndAliases ( )
415+ {
416+ var fixture = new JniFixtureBuilder ( ) ;
417+ fixture . Metadata . AddCustomAttribute ( EntityHandle . AssemblyDefinition , fixture . TypeMapCtor3 ,
418+ fixture . AttributeBlob ( "acme/orig/MyView[1]" , "Acme.Proxy, Fixture" , "Acme.Target, Fixture" ) ) ;
419+
420+ int fieldStart = fixture . NextFieldRid ;
421+ int methodStart = fixture . NextMethodRid ;
422+ TypeDefinitionHandle aliasHolder = fixture . AddType ( "Acme" , "AliasHolder" , fieldStart , methodStart ) ;
423+ fixture . Metadata . AddCustomAttribute ( aliasHolder , fixture . JavaPeerAliasesCtor1 ,
424+ fixture . StringArrayAttributeBlob ( "acme/orig/MyView[0]" , "acme/orig/MyView[1]" , "unmapped/Type[0]" ) ) ;
425+
426+ const string mappingText = "acme.orig.MyView -> a.b.C:\n " ;
427+ R8Mapping mapping = Mapping ( mappingText ) ;
428+ JniRewriteResult result = Rewrite ( fixture . Serialize ( ) , mapping ) ;
429+ AssertReverseScanMatchesRewrite ( result . Image , mapping , mappingText ) ;
430+ using var peReader = new PEReader ( ImmutableArray . Create ( result . Image ) ) ;
431+ MetadataReader reader = peReader . GetMetadataReader ( ) ;
432+
433+ CollectionAssert . AreEqual ( new [ ] { "a/b/C[1]" , "Acme.Proxy, Fixture" , "Acme.Target, Fixture" } ,
434+ AttributeStringArgs ( reader , reader . GetAssemblyDefinition ( ) . GetCustomAttributes ( ) , fixture . TypeMapCtor3 ) ) ;
435+ CollectionAssert . AreEqual ( new [ ] { "a/b/C[0]" , "a/b/C[1]" , "unmapped/Type[0]" } ,
436+ AttributeStringArrayArg ( reader , reader . GetTypeDefinition ( aliasHolder ) . GetCustomAttributes ( ) , fixture . JavaPeerAliasesCtor1 ) ) ;
437+ }
438+
439+ [ Test ]
440+ public void IdentifiesUtf8FieldRvaDataStructurally ( )
441+ {
442+ var fixture = new JniFixtureBuilder ( ) ;
443+ FieldDefinitionHandle nameField = fixture . AddUtf8Field ( "onClick" ) ;
444+ FieldDefinitionHandle signatureField = fixture . AddUtf8Field ( "(Lacme/orig/Callback;)V" ) ;
445+ FieldDefinitionHandle embeddedNullField = fixture . AddUtf8Field ( "onClick\0 not-padding" ) ;
446+
447+ using var peReader = new PEReader ( ImmutableArray . Create ( fixture . Serialize ( ) ) ) ;
448+ MetadataReader reader = peReader . GetMetadataReader ( ) ;
449+ FieldRvaTable table = FieldRvaTable . Read ( peReader , reader ) ;
450+
451+ Assert . AreEqual ( 3 , table . Entries . Count ) ;
452+
453+ FieldRvaEntry name = table . Get ( nameField ) ;
454+ Assert . IsNotNull ( name ) ;
455+ Assert . IsTrue ( name . IsUtf8Datum , "A __utf8_N mapped field must be recognised structurally." ) ;
456+ Assert . AreEqual ( "onClick" , name . Utf8Value ) ;
457+
458+ FieldRvaEntry signature = table . Get ( signatureField ) ;
459+ Assert . IsNotNull ( signature ) ;
460+ Assert . IsTrue ( signature . IsUtf8Datum ) ;
461+ Assert . AreEqual ( "(Lacme/orig/Callback;)V" , signature . Utf8Value ) ;
462+
463+ FieldRvaEntry embeddedNull = table . Get ( embeddedNullField ) ;
464+ Assert . IsNotNull ( embeddedNull ) ;
465+ Assert . IsFalse ( embeddedNull . IsUtf8Datum , "Non-zero data after the first NUL is not rewrite padding." ) ;
466+ }
467+
468+ [ Test ]
469+ public void RewritesUtf8FieldRvaJniNamesAndSignatures ( )
470+ {
471+ var fixture = new JniFixtureBuilder ( ) ;
472+
473+ FieldDefinitionHandle nameField = fixture . AddUtf8Field ( "onClick" ) ;
474+ FieldDefinitionHandle signatureField = fixture . AddUtf8Field ( "(Lacme/orig/Callback;)V" ) ;
475+ FieldDefinitionHandle classNameField = fixture . AddUtf8Field ( "acme/orig/Callback" ) ;
476+ FieldDefinitionHandle longNameField = fixture . AddUtf8Field ( "run" ) ;
477+
478+ int fieldStart = fixture . NextFieldRid ;
479+ int methodStart = fixture . NextMethodRid ;
480+ int ctorBody = fixture . EmitBody ( encoder => {
481+ encoder . OpCode ( ILOpCode . Ldarg_0 ) ;
482+ encoder . LoadString ( fixture . String ( "acme/orig/MyView" ) ) ;
483+ encoder . OpCode ( ILOpCode . Pop ) ;
484+ encoder . OpCode ( ILOpCode . Ret ) ;
485+ } ) ;
486+ fixture . AddVoidMethod ( ".ctor" , ctorBody ,
487+ MethodAttributes . Public | MethodAttributes . HideBySig | MethodAttributes . SpecialName | MethodAttributes . RTSpecialName ) ;
488+
489+ int registerBody = fixture . EmitBody ( encoder => {
490+ encoder . OpCode ( ILOpCode . Ldsflda ) ;
491+ encoder . Token ( nameField ) ;
492+ encoder . OpCode ( ILOpCode . Ldsflda ) ;
493+ encoder . Token ( signatureField ) ;
494+ encoder . OpCode ( ILOpCode . Pop ) ;
495+ encoder . OpCode ( ILOpCode . Pop ) ;
496+ encoder . OpCode ( ILOpCode . Ldsflda ) ;
497+ encoder . Token ( longNameField ) ;
498+ encoder . OpCode ( ILOpCode . Ldsflda ) ;
499+ encoder . Token ( signatureField ) ;
500+ encoder . OpCode ( ILOpCode . Pop ) ;
501+ encoder . OpCode ( ILOpCode . Pop ) ;
502+ encoder . OpCode ( ILOpCode . Ret ) ;
503+ } ) ;
504+ fixture . AddVoidMethod ( "RegisterNatives" , registerBody ) ;
505+
506+ // A JavaPeerProxy-derived type carries its JNI identity in its .ctor's only ldstr.
507+ fixture . AddType ( "Acme.Orig" , "MyViewProxy" , fieldStart , methodStart ,
508+ TypeAttributes . Public | TypeAttributes . Sealed | TypeAttributes . Class , fixture . JavaPeerProxyReference ) ;
509+
510+ byte [ ] source = fixture . Serialize ( ) ;
511+ const string mappingText =
512+ "acme.orig.MyView -> a.b.C:\n " +
513+ " void onClick(acme.orig.Callback) -> a\n " +
514+ " void run(acme.orig.Callback) -> aMuchLongerObfuscatedName\n " +
515+ "acme.orig.Callback -> a.b.Cb:\n " ;
516+ R8Mapping mapping = Mapping ( mappingText ) ;
517+ JniRewriteResult result = Rewrite ( source , mapping ) ;
518+ using ( var rewrittenReader = new PEReader ( ImmutableArray . Create ( result . Image ) ) ) {
519+ MetadataReader rewrittenMetadata = rewrittenReader . GetMetadataReader ( ) ;
520+ FieldRvaTable rewrittenFields = FieldRvaTable . Read ( rewrittenReader , rewrittenMetadata ) ;
521+ Assert . IsTrue ( rewrittenFields . Get ( nameField ) ? . IsUtf8Datum , "Rewritten method-name FieldRVA data should remain structurally recognizable." ) ;
522+ Assert . IsTrue ( rewrittenFields . Get ( signatureField ) ? . IsUtf8Datum , "Rewritten signature FieldRVA data should remain structurally recognizable." ) ;
523+ }
524+ AssertReverseScanMatchesRewrite ( result . Image , mapping , mappingText ) ;
525+
526+ using var peReader = new PEReader ( ImmutableArray . Create ( result . Image ) ) ;
527+ MetadataReader reader = peReader . GetMetadataReader ( ) ;
528+
529+ Assert . AreEqual ( "a" , ReadUtf8Field ( peReader , reader , nameField ) , "The method name is renamed using the owning proxy's JNI class." ) ;
530+ Assert . AreEqual ( "(La/b/Cb;)V" , ReadUtf8Field ( peReader , reader , signatureField ) ) ;
531+ Assert . AreEqual ( "a/b/Cb" , ReadUtf8Field ( peReader , reader , classNameField ) , "An unreferenced datum that is a known class name is still renamed." ) ;
532+ Assert . AreEqual ( "aMuchLongerObfuscatedName" , ReadUtf8Field ( peReader , reader , longNameField ) , "A longer datum is relocated into a wider __utf8_N slot." ) ;
533+
534+ // Growing a datum appends exactly one new sized type; no existing token moves.
535+ using var sourceReader = new PEReader ( ImmutableArray . Create ( source ) ) ;
536+ MetadataReader before = sourceReader . GetMetadataReader ( ) ;
537+ Assert . AreEqual ( before . GetTableRowCount ( TableIndex . TypeDef ) + 1 , reader . GetTableRowCount ( TableIndex . TypeDef ) ) ;
538+ AssertTableRowCountsMatch ( before , reader , TableIndex . TypeDef , TableIndex . ClassLayout , TableIndex . NestedClass ) ;
539+ }
540+
541+ static string ReadUtf8Field ( PEReader peReader , MetadataReader reader , FieldDefinitionHandle field )
542+ {
543+ FieldDefinition definition = reader . GetFieldDefinition ( field ) ;
544+ int rva = definition . GetRelativeVirtualAddress ( ) ;
545+ Assert . AreNotEqual ( 0 , rva , "Field has no RVA." ) ;
546+
547+ PEMemoryBlock block = peReader . GetSectionData ( rva ) ;
548+ var bytes = new List < byte > ( ) ;
549+ BlobReader blob = block . GetReader ( 0 , Math . Min ( block . Length , 256 ) ) ;
550+ for ( byte b = blob . ReadByte ( ) ; b != 0 ; b = blob . ReadByte ( ) ) {
551+ bytes . Add ( b ) ;
552+ }
553+ return System . Text . Encoding . UTF8 . GetString ( bytes . ToArray ( ) ) ;
554+ }
555+
556+ [ Test ]
557+ public void FailsWhenASharedUtf8DatumNeedsTwoDifferentNames ( )
558+ {
559+ var fixture = new JniFixtureBuilder ( ) ;
560+
561+ FieldDefinitionHandle shared = fixture . AddUtf8Field ( "go" ) ;
562+ FieldDefinitionHandle signature = fixture . AddUtf8Field ( "()V" ) ;
563+
564+ AddProxy ( fixture , "acme/orig/P1" , shared , signature ) ;
565+ AddProxy ( fixture , "acme/orig/P2" , shared , signature ) ;
566+
567+ var exception = Assert . Throws < JniRewriteException > ( ( ) => Rewrite ( fixture . Serialize ( ) , Mapping (
568+ "acme.orig.P1 -> a.b.P1:\n " +
569+ " void go() -> z\n " +
570+ "acme.orig.P2 -> a.b.P2:\n " +
571+ " void go() -> q\n " ) ) ) ;
572+ StringAssert . Contains ( "shared" , exception . Message . ToLowerInvariant ( ) ) ;
573+ }
574+
575+ static void AddProxy ( JniFixtureBuilder fixture , string jniName , FieldDefinitionHandle nameField , FieldDefinitionHandle signatureField )
576+ {
577+ int fieldStart = fixture . NextFieldRid ;
578+ int methodStart = fixture . NextMethodRid ;
579+
580+ fixture . AddVoidMethod ( ".ctor" , fixture . EmitBody ( encoder => {
581+ encoder . OpCode ( ILOpCode . Ldarg_0 ) ;
582+ encoder . LoadString ( fixture . String ( jniName ) ) ;
583+ encoder . OpCode ( ILOpCode . Pop ) ;
584+ encoder . OpCode ( ILOpCode . Ret ) ;
585+ } ) , MethodAttributes . Public | MethodAttributes . HideBySig | MethodAttributes . SpecialName | MethodAttributes . RTSpecialName ) ;
586+
587+ fixture . AddVoidMethod ( "RegisterNatives" , fixture . EmitBody ( encoder => {
588+ encoder . OpCode ( ILOpCode . Ldsflda ) ;
589+ encoder . Token ( nameField ) ;
590+ encoder . OpCode ( ILOpCode . Ldsflda ) ;
591+ encoder . Token ( signatureField ) ;
592+ encoder . OpCode ( ILOpCode . Pop ) ;
593+ encoder . OpCode ( ILOpCode . Pop ) ;
594+ encoder . OpCode ( ILOpCode . Ret ) ;
595+ } ) ) ;
596+
597+ fixture . AddType ( "Acme.Orig" , jniName . Replace ( '/' , '_' ) , fieldStart , methodStart ,
598+ TypeAttributes . Public | TypeAttributes . Sealed | TypeAttributes . Class , fixture . JavaPeerProxyReference ) ;
599+ }
600+
392601 [ Test ]
393602 public void SharedLoadedStringGetsOwnerSpecificReplacements ( )
394603 {
@@ -769,6 +978,74 @@ public void RewrittenAssemblyStillMatchesItsPortablePdb ()
769978 }
770979 }
771980
981+ [ Test ]
982+ public void PreservesMappedFieldDataThatIsNotAJniDatum ( )
983+ {
984+ var fixture = new JniFixtureBuilder ( ) ;
985+
986+ // A plain C#-style array initializer blob: not a __utf8_N datum, so it must survive
987+ // byte-for-byte.
988+ var payload = new byte [ ] { 0xDE , 0xAD , 0xBE , 0xEF , 0x01 , 0x02 , 0x03 , 0x04 } ;
989+ TypeDefinitionHandle enclosing = fixture . EnsurePrivateImplementationDetails ( ) ;
990+ int fieldStart = fixture . NextFieldRid ;
991+ int methodStart = fixture . NextMethodRid ;
992+ TypeDefinitionHandle arrayType = fixture . AddType ( null , "__StaticArrayInitTypeSize=8" , fieldStart , methodStart ,
993+ TypeAttributes . NestedPrivate | TypeAttributes . ExplicitLayout | TypeAttributes . Sealed | TypeAttributes . AnsiClass ,
994+ fixture . ValueTypeReference ) ;
995+ fixture . Metadata . AddTypeLayout ( arrayType , packingSize : 1 , size : ( uint ) payload . Length ) ;
996+ fixture . Metadata . AddNestedType ( arrayType , enclosing ) ;
997+
998+ var signature = new BlobBuilder ( ) ;
999+ new BlobEncoder ( signature ) . FieldSignature ( ) . Type ( arrayType , isValueType : true ) ;
1000+ int rva = fixture . MappedFieldData . Count ;
1001+ fixture . MappedFieldData . WriteBytes ( payload ) ;
1002+ FieldDefinitionHandle dataField = fixture . Metadata . AddFieldDefinition (
1003+ FieldAttributes . Static | FieldAttributes . Assembly | FieldAttributes . HasFieldRVA ,
1004+ fixture . Metadata . GetOrAddString ( "ArrayData" ) , fixture . Metadata . GetOrAddBlob ( signature ) ) ;
1005+ fixture . Metadata . AddFieldRelativeVirtualAddress ( dataField , rva ) ;
1006+
1007+ JniRewriteResult result = Rewrite ( fixture . Serialize ( ) , Mapping ( "acme.orig.Nothing -> a.b.N:\n " ) ) ;
1008+
1009+ using var peReader = new PEReader ( ImmutableArray . Create ( result . Image ) ) ;
1010+ MetadataReader reader = peReader . GetMetadataReader ( ) ;
1011+ int newRva = reader . GetFieldDefinition ( dataField ) . GetRelativeVirtualAddress ( ) ;
1012+ Assert . AreNotEqual ( 0 , newRva ) ;
1013+ CollectionAssert . AreEqual ( payload , peReader . GetSectionData ( newRva ) . GetReader ( 0 , payload . Length ) . ReadBytes ( payload . Length ) ) ;
1014+ }
1015+
1016+ [ Test ]
1017+ public void RejectsFieldRvaValueTypeWithoutAnExplicitClassLayoutSize ( )
1018+ {
1019+ // A mapped value type with no ClassLayout row (or a zero size) cannot be sized safely:
1020+ // summing its instance fields would be a guess about the CLR's actual layout, and a
1021+ // wrong guess risks truncating - or reading past the end of - the mapped data. The
1022+ // rewriter must refuse rather than take that risk.
1023+ var fixture = new JniFixtureBuilder ( ) ;
1024+
1025+ TypeDefinitionHandle enclosing = fixture . EnsurePrivateImplementationDetails ( ) ;
1026+ int fieldStart = fixture . NextFieldRid ;
1027+ int methodStart = fixture . NextMethodRid ;
1028+ TypeDefinitionHandle unsizedType = fixture . AddType ( null , "__UnsizedBlob" , fieldStart , methodStart ,
1029+ TypeAttributes . NestedPrivate | TypeAttributes . ExplicitLayout | TypeAttributes . Sealed | TypeAttributes . AnsiClass ,
1030+ fixture . ValueTypeReference ) ;
1031+ fixture . Metadata . AddNestedType ( unsizedType , enclosing ) ;
1032+ // Deliberately no fixture.Metadata.AddTypeLayout (...) call: the type has no
1033+ // ClassLayout row at all.
1034+
1035+ var signature = new BlobBuilder ( ) ;
1036+ new BlobEncoder ( signature ) . FieldSignature ( ) . Type ( unsizedType , isValueType : true ) ;
1037+ int rva = fixture . MappedFieldData . Count ;
1038+ fixture . MappedFieldData . WriteBytes ( new byte [ ] { 0x01 , 0x02 , 0x03 , 0x04 } ) ;
1039+ FieldDefinitionHandle dataField = fixture . Metadata . AddFieldDefinition (
1040+ FieldAttributes . Static | FieldAttributes . Assembly | FieldAttributes . HasFieldRVA ,
1041+ fixture . Metadata . GetOrAddString ( "UnsizedData" ) , fixture . Metadata . GetOrAddBlob ( signature ) ) ;
1042+ fixture . Metadata . AddFieldRelativeVirtualAddress ( dataField , rva ) ;
1043+
1044+ byte [ ] source = fixture . Serialize ( ) ;
1045+ var ex = Assert . Throws < JniRewriteException > ( ( ) => Rewrite ( source , Mapping ( "acme.orig.Nothing -> a.b.N:\n " ) ) ) ;
1046+ StringAssert . Contains ( "ClassLayout" , ex . Message ) ;
1047+ }
1048+
7721049 [ Test ]
7731050 public void RewrittenAssemblyLoadsAndRunsInTheRuntime ( )
7741051 {
0 commit comments