Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

[class-parse] support Module AttributeInfo #1097

Merged
merged 2 commits into from
Apr 24, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
119 changes: 119 additions & 0 deletions src/Xamarin.Android.Tools.Bytecode/AttributeInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ public class AttributeInfo {
public const string InnerClasses = "InnerClasses";
public const string LocalVariableTable = "LocalVariableTable";
public const string MethodParameters = "MethodParameters";
public const string Module = "Module";
public const string ModulePackages = "ModulePackages";
public const string Signature = "Signature";
public const string SourceFile = "SourceFile";
public const string StackMapTable = "StackMapTable";
Expand Down Expand Up @@ -79,6 +81,8 @@ public string Name {
{ typeof (InnerClassesAttribute), InnerClasses },
{ typeof (LocalVariableTableAttribute), LocalVariableTable },
{ typeof (MethodParametersAttribute), MethodParameters },
{ typeof (ModuleAttribute), Module },
{ typeof (ModulePackagesAttribute), ModulePackages },
{ typeof (RuntimeVisibleAnnotationsAttribute), RuntimeVisibleAnnotations },
{ typeof (RuntimeInvisibleAnnotationsAttribute), RuntimeInvisibleAnnotations },
{ typeof (SignatureAttribute), Signature },
Expand All @@ -98,6 +102,7 @@ internal static string GetAttributeName<T>()
public static AttributeInfo CreateFromStream (ConstantPool constantPool, Stream stream)
{
var nameIndex = stream.ReadNetworkUInt16 ();
var constant = constantPool [nameIndex];
var name = ((ConstantPoolUtf8Item) constantPool [nameIndex]).Value;
var attr = CreateAttribute (name, constantPool, nameIndex, stream);
return attr;
Expand All @@ -114,6 +119,8 @@ static AttributeInfo CreateAttribute (string name, ConstantPool constantPool, us
case InnerClasses: return new InnerClassesAttribute (constantPool, nameIndex, stream);
case LocalVariableTable: return new LocalVariableTableAttribute (constantPool, nameIndex, stream);
case MethodParameters: return new MethodParametersAttribute (constantPool, nameIndex, stream);
case Module: return new ModuleAttribute (constantPool, nameIndex, stream);
case ModulePackages: return new ModulePackagesAttribute (constantPool, nameIndex, stream);
case RuntimeVisibleAnnotations: return new RuntimeVisibleAnnotationsAttribute (constantPool, nameIndex, stream);
case RuntimeInvisibleAnnotations: return new RuntimeInvisibleAnnotationsAttribute (constantPool, nameIndex, stream);
case RuntimeInvisibleParameterAnnotations: return new RuntimeInvisibleParameterAnnotationsAttribute (constantPool, nameIndex, stream);
Expand Down Expand Up @@ -503,6 +510,118 @@ public override string ToString ()
}
}

// https://docs.oracle.com/javase/specs/jvms/se11/html/jvms-4.html#jvms-4.7.25
public sealed class ModuleAttribute : AttributeInfo
{
ushort moduleNameIndex;
public string ModuleName {
get {return ((ConstantPoolModuleItem) ConstantPool [moduleNameIndex]).Name.Value;}
}

public ModuleFlags ModuleFlags { get; private set; }

ushort moduleVersionIndex;
public string? ModuleVersion {
get {return ((ConstantPoolUtf8Item) ConstantPool [moduleVersionIndex])?.Value;}
}

public Collection<ModuleRequiresInfo> Requires {get;} = new ();
public Collection<ModuleExportsPackageInfo> Exports {get;} = new ();
public Collection<ModuleOpensPackageInfo> Opens {get;} = new ();
public Collection<ConstantPoolClassItem> Uses {get;} = new ();
public Collection<ModuleProvidesInfo> Provides {get;} = new ();

public ModuleAttribute (ConstantPool constantPool, ushort nameIndex, Stream stream)
: base (constantPool, nameIndex, stream)
{
var attribute_length = stream.ReadNetworkUInt32 ();

moduleNameIndex = stream.ReadNetworkUInt16 ();
ModuleFlags = (ModuleFlags) stream.ReadNetworkUInt16 ();
moduleVersionIndex = stream.ReadNetworkUInt16 ();

var requires_count = stream.ReadNetworkUInt16 ();
for (int i = 0; i < requires_count; ++i) {
Requires.Add (new ModuleRequiresInfo (constantPool, stream));
}

var exports_count = stream.ReadNetworkUInt16 ();
for (int i = 0; i < exports_count; ++i) {
Exports.Add (new ModuleExportsPackageInfo (constantPool, stream));
}

var opens_count = stream.ReadNetworkUInt16 ();
for (int i = 0; i < opens_count; ++i) {
Opens.Add (new ModuleOpensPackageInfo (constantPool, stream));
}

var uses_count = stream.ReadNetworkUInt16 ();
for (int i = 0; i < uses_count; ++i) {
var uses_index = stream.ReadNetworkUInt16 ();
Uses.Add ((ConstantPoolClassItem) constantPool [uses_index]);
}

var provides_count = stream.ReadNetworkUInt16 ();
for (int i = 0; i < provides_count; ++i) {
Provides.Add (new ModuleProvidesInfo (constantPool, stream));
}
}

public override string ToString ()
{
var s = new StringBuilder ()
.Append ("Module(").AppendLine ()
.Append (" ").Append (nameof (ModuleName)).Append ("='").Append (ModuleName).AppendLine ("', ")
.Append (" ").Append (nameof (ModuleVersion)).Append ("='").Append (ModuleVersion).Append ("'");
AppendString (s, nameof (Requires), Requires);
AppendString (s, nameof (Exports), Exports);
AppendString (s, nameof (Opens), Opens);
AppendString (s, nameof (Uses), Uses.Select (u => $"UsesService({u.Name})").ToList ());
AppendString (s, nameof (Provides), Provides);
s.Append (")");

return s.ToString ();
}

static StringBuilder AppendString<T> (StringBuilder s, string collectionName, IList<T> items)
{
if (items.Count == 0) {
return s;
}
s.AppendLine (",");
s.Append (" ").Append (collectionName).AppendLine ("={");
s.Append (" ").Append (items [0]);
for (int i = 1; i < items.Count; ++i) {
s.AppendLine (",");
s.Append (" ");
s.Append (items [i]);
}
return s.Append ("}");
}
}

// https://docs.oracle.com/javase/specs/jvms/se11/html/jvms-4.html#jvms-4.7.26
public sealed class ModulePackagesAttribute : AttributeInfo {
public Collection<ConstantPoolPackageItem> Packages {get;} = new ();

public ModulePackagesAttribute (ConstantPool constantPool, ushort nameIndex, Stream stream)
: base (constantPool, nameIndex, stream)
{
var attribute_length = stream.ReadNetworkUInt32 ();

var package_count = stream.ReadNetworkUInt16 ();
for (int i = 0; i < package_count; ++i) {
var package_index = stream.ReadNetworkUInt16 ();
Packages.Add ((ConstantPoolPackageItem) constantPool [package_index]);
}
}

public override string ToString ()
{
return $"ModulePackages({{{string.Join (", ", Packages.Select (p => p.Name.Value))}}})";
}
}

// https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.7.16
public sealed class RuntimeVisibleAnnotationsAttribute : AttributeInfo
{
Expand Down
15 changes: 13 additions & 2 deletions src/Xamarin.Android.Tools.Bytecode/ClassFile.cs
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,15 @@ public ClassFile (Stream stream)
Attributes = new AttributeCollection (ConstantPool, stream);

int e = stream.ReadByte ();
if (e >= 0)
throw new BadImageFormatException ("Stream has trailing data?!");
if (e >= 0) {
var trailing = new System.Text.StringBuilder ();
trailing.AppendFormat ("{0:x2}", (byte) e);
while ((e = stream.ReadByte ()) >= 0) {
trailing.Append (" ");
trailing.AppendFormat ("{0:x2}", (byte) e);
}
throw new BadImageFormatException ($"Stream has trailing data?! {{{trailing}}}");
}
}

public static bool IsClassFile (Stream stream)
Expand Down Expand Up @@ -213,6 +220,10 @@ public enum ClassAccessFlags {
Synthetic = 0x1000,
Annotation = 0x2000,
Enum = 0x4000,
Module = 0x8000,

// This is not a real Java ClassAccessFlags, it is used to denote non-exported public types
Internal = 0x10000000,
}
}

50 changes: 47 additions & 3 deletions src/Xamarin.Android.Tools.Bytecode/ClassPath.cs
Original file line number Diff line number Diff line change
Expand Up @@ -88,9 +88,6 @@ static bool ShouldLoadEntry (ZipArchiveEntry entry)
if (entry.Length == 0)
return false;

if (entry.Name == "module-info.class")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this going to undo #1093?

That is, will this write module-info.class into api.xml?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, it will "undo" #1093, and No, it will not write module-info.class into api.xml. (I should explicitly call this out in the commit message; doh!)

See FixupModuleVisibility(), in particular https://github.com/xamarin/java.interop/pull/1097/files#diff-cd606380b5886cb27bfcf4f255f042d4cb739a6a33545920a305cba1113dac74R386

We do two things in FixupModuleVisibility():

  1. We collect all of the ClassFile entries of type ClassAccessFlags.Module, and remove them from classFiles. This prevents them from being written into api.xml.
  2. For the ClassFile entries in (1), we find the set of exported packages (in publicPackages), and (2) iterate over every entry in classFiles. Any classFiles entry with a package name outside of publicPackages has its visibility updated to ClassAccessFlags.Internal.

One problem with this approach, not considered until just now, is that if class-parse is parsing multiple .jar files and writing out a single api.xml, then the module-info.class information would be applied to entries from a different .jar. This may result in "weird" behavior, and I'll need to think about this.

return false;

if (entry.Name.EndsWith (".jnilib", StringComparison.OrdinalIgnoreCase))
return false;

Expand All @@ -111,6 +108,14 @@ public void Add (ClassFile classFile)
classFiles.Add (classFile);
}

public void Add (ClassPath classPath, bool removeModules = true)
{
classPath.FixupModuleVisibility (removeModules);
foreach (var c in classPath.classFiles) {
Add (c);
}
}

public ReadOnlyDictionary<string, List<ClassFile>> GetPackages ()
{
return new ReadOnlyDictionary<string, List<ClassFile>> (classFiles
Expand Down Expand Up @@ -360,6 +365,7 @@ public XElement ToXElement ()
FixUpParametersFromClasses ();

KotlinFixups.Fixup (classFiles);
FixupModuleVisibility (removeModules: true);

var packagesDictionary = GetPackages ();
var api = new XElement ("api",
Expand All @@ -375,6 +381,42 @@ packagesDictionary [p].OrderBy (c => c.ThisClass.Name.Value, StringComparer.Ordi
return api;
}

public void FixupModuleVisibility (bool removeModules)
{
var publicPackages = new HashSet<string> ();

var moduleFiles = classFiles.Where (c => c.AccessFlags == ClassAccessFlags.Module)
.ToList ();
if (moduleFiles.Count == 0) {
return;
}
foreach (var moduleFile in moduleFiles) {
if (removeModules) {
classFiles.Remove (moduleFile);
}
foreach (var moduleAttr in moduleFile.Attributes.OfType<ModuleAttribute> ()) {
foreach (var export in moduleAttr.Exports) {
publicPackages.Add (export.Exports);
}
}
}

foreach (var c in classFiles) {
if (!c.AccessFlags.HasFlag (ClassAccessFlags.Public)) {
continue;
}
var jniName = c.ThisClass.Name.Value;
var packageEnd = jniName.LastIndexOf ('/');
if (packageEnd < 0) {
continue;
}
var package = jniName.Substring (0, packageEnd);
if (!publicPackages.Contains (package)) {
c.AccessFlags = KotlinFixups.SetVisibility (c.AccessFlags, ClassAccessFlags.Internal);
}
}
}

public void SaveXmlDescription (string fileName)
{
var encoding = new UTF8Encoding (encoderShouldEmitUTF8Identifier: false);
Expand All @@ -395,5 +437,7 @@ public void SaveXmlDescription (TextWriter textWriter)
contents.Save (writer);
textWriter.WriteLine ();
}

public IEnumerable<ClassFile> GetClassFiles () => classFiles;
}
}
2 changes: 1 addition & 1 deletion src/Xamarin.Android.Tools.Bytecode/Kotlin/KotlinFixups.cs
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ static void FixupClassVisibility (ClassFile klass, KotlinClass metadata)
}

// Passing null for 'newVisibility' parameter means 'package-private'
static ClassAccessFlags SetVisibility (ClassAccessFlags existing, ClassAccessFlags? newVisibility)
internal static ClassAccessFlags SetVisibility (ClassAccessFlags existing, ClassAccessFlags? newVisibility)
{
// First we need to remove any existing visibility flags,
// without modifying other flags like Abstract
Expand Down
Loading