lundi 30 octobre 2023

How to inject a method into a class at runtime without ASM?

In past versions of the bytecode manipulation framework Mixin, there was a method called prepareConfigs(MixinEnvironment). A nonstandard, but very commonly-used, way to bootstrap Mixins at a certain phase of the lifecycle was to access and call that method using Reflection.

In later versions of the framework, that method's signature has been changed to prepareConfigs(MixinEnvironment, Extensions), causing the Reflection-based usages to break.

The owner of the library has made it clear that breaking this was intentional and will not allow a fix. However, even though it was bad practice to use that strategy, the usage was common enough that end-users are unable to use many apps that relied on the framework.

I would like to make a compatibility patch that adds the old method back to the class to stop the crashes. Problem is, this requires injecting a method into a class that is restricted from being transformed by ASM.

The only requirement is that the method does not throw a NoSuchMethodException when accessed via reflection, since I'm easily able to implement their intended behavior somewhere else. It doesn't actually need to do anything, it just needs to stop the exception from being thrown.

I've heard people offhand-mention a way to declare class members that can only be accessed by Reflection, but I couldn't find any details on it.

Is there a way to do what I need without ASM?





dimanche 29 octobre 2023

Type Load Exception On Method Load

I have a Visual Studio 2023 solution containing 32 projects. The projects extensively reference each other. 31 of the projects are class libraries, while the last one is a console application used whenever I want to test one of the functions in my library, so I don't have to create a new temporary one each time. Visual Studio's Immediate Window has never worked for me, even before this issue. All the projects are written in C# 11, and use .NET 7.0. Many of the projects reference the NuGet package System.Collections.Immutable.

I was attempting to write a Crawler, like a Web Crawler, but for types. It would iterate though all static/nonstatic members of a type, add their parameter types and return types (if present) to a HashSet<Type>. It would then recursively do the same for each type that was added. It was designed so that it would only ever iterate though each time once.

It did this by creating a List<Type>, and every time a type was added to the HashSet<Type> that did not already exist, it would add it to the list. After the list was complete, it would recursively do the same for each type in the list. If a type was, or contained in its generic parameters (if present), a generic parameter, it would be substituted with its definition. There were also some functions to cover pointers, references, and arrays.

I ran the function on an object, and it worked fine. I got the type for the object using C#'s typeof(?) method. However, then I ran the function on a static class in one of my projects, and it threw an exception.

At this point, it is worth mentioning the projects that are involved with this. I have one console application project (Test) in which I am executing the crawler in a second project (Brendan.IC.Typed.Execution), which was supposed to crawl through a static class in a third project (Brendan.IC.Typed). The latter project references System.Collections.Immutable. System.Collections.Immutable most likely has nothing to do with this, but I think its worth mentioning everything, since I have absolutely no idea what the problem is.

The exception was a System.TypeLoadException.

System.TypeLoadException: 'Could not load type 'Brendan.IC.Typed.TypedEvolutionLookup`1' from assembly 'Brendan.IC.Typed, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null'.'

The static class I had called the crawler on was Brendan.IC.Typed.TypedEvolution. One of its static methods contained a parameter of type Brendan.IC.Typed.TypedEvolutionLookup<>. This is how the crawler found it. It then was unable to load it. The exception was thrown in the MethodInfo.GetParameters() function.

Initially, I assumed this was some problem with my crawler function. I cleaned and rebuilt the solution, my normal first step in handling errors, but to no effect. I recompiled the solution multiple times in both Debug and Release mode, to no effect. I checked my code for anything that might effect type loading. I found nothing. Finally, I decided to load the offending type in my top-level statements. It threw an exception, the same exception it had previously.

System.TypeLoadException: 'Could not load type 'CodeInterpretation.Typed.TypedEvolutionLookup`1' from assembly 'Brendan.IC.Typed, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null'.'

Here was my top-level statement:

using Brendan.IC.Typed;
using Brendan.IC.Typed.Execution;
using static System.Console;
Type bigType = typeof(TypedEvolutionLookup<int>);
Crawler.Crawl(bigType);
ReadKey(true);

I simplified it to this:

using Brendan.IC.Typed;
using static System.Console;
Type bigType = typeof(TypedEvolutionLookup<int>);
ReadKey(true);

This is when things started to get weird... real weird.

First of all, I noticed that there was no line number or call stack associated with the exception.

A picture of the exception.

Upon selecting Show Call Stack:

A picture of the call stack.

Here's my full screen:

A fullscreen screenshot of the exception.

Usually, when I get an exception, I can add a WriteLine() right before the line I suspect, and it will print to the console before the program terminates due to the exception. I did so, and added a breakpoint on the same line, intending to step through the program. Finally, I added a generic parameter to the generic type definition. But the program didn't even hit the breakpoint! Nor did it print to the console!

New code:

using Brendan.IC.Typed;
using static System.Console;
WriteLine("hi");
Type bigType = typeof(TypedEvolutionLookup<int>);
ReadKey(true);

Screenshot:

Screenshot of exception.

I put it in release mode, too. As expected, it did not throw an error, since it optimized out the unnecessary assignment. Nor did it hit the breakpoint, obviously.

I removed the 4th line, and it then work fine. I added it back in, it broke. I tried getting a different type from the Brendan.IC.Typed assembly. It worked fine. I replaced the WriteLine() statement with another ReadKey(true) statement. It broke.

using Brendan.IC.Typed;
using static System.Console;
ReadKey(true);
Type bigType = typeof(TypedEvolutionLookup<int>);
ReadKey(true);

A screenshot of the error for the latter case.

I got rid of the generic parameter. There was no change:

A screenshot of the error.

What worries me is that I think the error is happening, not with the execution of the typeof statement, but in the compiler for the IL! The error is being thrown between when I compile it, and when the top-level statements are executed! There is only one thing I can think of that would fit those constraints, and depend on the existence of a typeof line, and that it the IL runtime compiler itself!!! Yet the type is being found easily by the C#-to-IL compiler!

And the error is not happening with any of the other types in the assembly! Unfortunately, the TypedEvolutionLookup<> struct is the only generic type in the Brendan.IC.Typed assembly. However, it is not the only readonly struct. I have, though, tested some of the other readonly structs, and they do not have the same problem. The IL compiler seems to, for some reason, single out this one specific type, and, dependent or not on generics, this should not be happening.

I have, multiple times, restarted my computer, in case there is some secret RAM type-cache that I am not aware of. I have, multiple times, cleaned and rebuilt the solution, in case there is something weird with that. I have, multiple times, deleted the output directories of all my projects, in case some past versions are interfering with the present ones.

(All my class libraries compile to the same output directory; however, the console application compiles to its own, so as to avoid confusion.)

There has been no change. I do not understand this. I want to get the System.Type for this struct. Why has the IL compiler singled out this specific one? It has no funky attributes, except for the occasional [MaybeNullWhen(false)] in the parameters of its methods.

Why could this be happening, and how might I avoid it?





vendredi 27 octobre 2023

MethodHandle Lookup Issues

I've recently needed to make a relatively simple events system. (Id -> Consumer) This would be able to subscribe a Method based off annotation. Then I remembered that Java isn't C# and attempted to look for a way to accomplish this. After many similar threads, I came across this GitHub Gist, and attempted to implement it. I did so successfully, however did not realize early enough that the MethodHandles.lookup() method is location specific and only applies to the class that calls it.

ConsumerFactory

public class ConsumerFactory {
    private final Method consumerMethod;
    private final MethodType consumerMethodType;

    private final Map<Method, Consumer<?>> methodCache = new HashMap<>();

    public ConsumerFactory() {
        consumerMethod = findLambdaMethod(Consumer.class);
        consumerMethodType = MethodType.methodType(consumerMethod.getReturnType(), consumerMethod.getParameterTypes());
    }

    public <T, L> Consumer<T> createConsumer(L instance, Method implMethod) throws Throwable {
        Consumer<T> cached = (Consumer<T>) methodCache.get(implMethod);
        if(cached==null) {
            Class<?> implType = implMethod.getDeclaringClass();

            MethodHandles.Lookup lookup = MethodHandles.lookup().in(implType);
            MethodType implMethodType = MethodType.methodType(implMethod.getReturnType(), implMethod.getParameterTypes());
            MethodHandle implMethodHandle = lookup.findVirtual(implType, implMethod.getName(), implMethodType);

            MethodType invokedMethodType = MethodType.methodType(Consumer.class, implType);

            CallSite metaFactory = LambdaMetafactory.metafactory(
                    lookup,
                    consumerMethod.getName(), invokedMethodType, consumerMethodType,
                    implMethodHandle, implMethodType);

            MethodHandle factory = metaFactory.getTarget();
            Consumer<T> consumer = (Consumer<T>) factory.invoke(instance);
            methodCache.put(implMethod, consumer);
            return consumer;
        }
        return cached;
    }

    private Method findLambdaMethod(Class<?> type) {
        if (!type.isInterface()) {
            throw new IllegalArgumentException("This must be interface: " + type);
        }
        Method[] methods = getAllMethods(type);
        if (methods.length == 0) {
            throw new IllegalArgumentException("No methods in: " + type.getName());
        }
        Method targetMethod = null;
        for (Method method : methods) {
            if (isInterfaceMethod(method)) {
                if (targetMethod != null) {
                    throw new IllegalArgumentException("This isn't functional interface: " + type.getName());
                }
                targetMethod = method;
            }
        }
        if (targetMethod == null) {
            throw new IllegalArgumentException("No method in: " + type.getName());
        }
        return targetMethod;
    }
    private Method[] getAllMethods(Class<?> type) {
        LinkedList<Method> result = new LinkedList<>();
        Class<?> current = type;
        do {
            result.addAll(Arrays.asList(current.getMethods()));
        } while ((current = current.getSuperclass()) != null);
        return result.toArray(new Method[0]);
    }
    private boolean isInterfaceMethod(Method method) {
        return !method.isDefault() && Modifier.isAbstract(method.getModifiers());
    }
}

Is their anything I could do to still have this sort of idea function? Ideally end up as something like Map<String, Consumer<Event>>. I was considering forcefully creating a Lookup class, but I'm not sure if that would be the best idea.

Thank you all for the help!





Get the type name of a Dictionary with generics in C#

Is there an easy or elegant way to get the type name of a Dictionary with the generic types in C#?

Currently I have this code works:

if (property.PropertyType.Name == "Dictionary`2")
{
    Type[] arguments = property.PropertyType.GetGenericArguments();
    Type keyType = arguments[0];
    Type valueType = arguments[1];
    properties[property.Name] = $"Dictionary<{keyType.Name},{valueType.Name}>";
}

I was wondering whether there is more easy way of achieving this formatted string that I'm looking for, like: "Dictionary<type, type>"





mercredi 25 octobre 2023

Blazor validate custom editor control bound to property by name

In my Blazor server app, I created a CustomEdit control to edit DateTime properties of an object by name:

NOTE this is an illustration - using an existing DateTime editor is not the point here, the point is I am binding to a property by its name!

@typeparam TItem;
@inherits ComponentBase;  
@if (Context != null) {
  <InputText @bind-Value="@DateStr" />
  <ValidationMessage For="@(() => DateStr)" />
}
@code {
  [Parameter]
  public TItem Context { get; set; }

  [Parameter]
  public string BoundPropertyName { get; set; }

  [Parameter]
  public string DateFormat { get; set; } = "dd.MM.yyyy";

  protected override void OnParametersSet() {
    if (_pInfo == null) {
      _pInfo = typeof(TItem).GetProperty(BoundPropertyName);
    }
    base.OnParametersSet();
  }
  PropertyInfo _pInfo;

  bool isValid(DateTime result) {
    if (_pInfo == null || Context == null) {
      return false;
    }
    // --- validation
    var valContext = new ValidationContext(Context) { MemberName = BoundPropertyName };
    var validationResults = new List<ValidationResult>();
    return Validator.TryValidateProperty(result, valContext, validationResults);
  }
  string DateStr {
    get => _pInfo != null && Context != null ? ((DateTime)_pInfo.GetValue(Context)).ToString(DateFormat) : string.Empty;
    set {
      DateTime result;
      if (DateTime.TryParseExact(value, DateFormat, CultureInfo.InvariantCulture, DateTimeStyles.None, out result) && isValid(result)) {
        _pInfo.SetValue(Context, result);
      }
    }
  }
}

I created an edit form bound to a PersonData model with a birthday as follows within the edit form:

<CustomEdit TItem="PersonData" BoundPropertyName="@nameof(PersonData.BirthDate)" Context="@model" />

Now, I see the validation triggers as expected, returns false for an empty string for example, but my validation message is not showing and the editor box appears as valid (green) as it shouldn't (PersonDate.BirthDate is flagged as [Required] and also with a custom ValidDateAttribute that has some internal logic). Why is the message not showing?

As a side-note: if I use an existing DateTime editor and directly @bind-Value="@context.Birthday", validation works perfectly and shows the validation errors I want to see...





dimanche 22 octobre 2023

C++ how to get the first and the last element in an std::vector by its base memory address

To me, a vector layout looks something like this:

struct MyViewOnHowVectorLooksLike
{
public:
   int* _first, * _last, * _end;
};

Known Parameters:

  1. Address of the std::vector<Type> object
  2. size of the <Type>

Questions:

  1. Find the address of the first element and the last element in the vector

Is there any way to access the first and last elements in a std::vector by its base memory address, which is &Test in the above test case, not by calling any build-in method of the std::vector function eg [0], .front(), etc; (The reason why I'm doing this is to have a static reflection system which could output the content of any given written vector type)

struct FTestData {
    int data;
};

class TestClass {
public:
    // test array
    std::vector<FTestData> testArray;

    TestClass(){}
};

// TEST CASE
FTestData testCon1 = FTestData();
testCon1.data = 111;
FTestData testCon2 = FTestData();
testCon2.data = 222;

TestClass Test = TestClass();
Test.testArray.push_back(testCon1);
Test.testArray.push_back(testCon2);

std::vector<FTestData>* TestData = (std::vector<FTestData>*)((size_t)&Test);
size_t ptr = (size_t)TestData;
FTestData& TestElement1 = (*TestData)[0];
size_t realFirstElementAddress = (size_t)&TestElement1;

// try to get the address of the first element in the vector but it  gives me the wrong address
size_t CustomFirstElementAddress = (*(size_t*)(ptr));

// expecting those two to be the same but it wasnt
assert(realFirstElementAddress == CustomFirstElementAddress);

I thought the _First and _Last are right next to the base address, but it keeps outputting the wrong results.

AddOn: The real problem I am trying to solve is to have a detail panel with my own reflection system, which could allow me do something like this. It works for most common types, and nested class.

{
    if (field.Type == "int") {
        int location[1];
        auto& value = *(int*)((size_t)&Component + Offset + field.Offset);
        memcpy(location, &value, sizeof(location));
        if (ImGui::DragInt(fieldName.c_str(), location, 0.1f, 0.0f, 0.0f, "%.1f"))
        {
            memcpy(&value, location, sizeof(location));
        }
    }
    else if (field.Type == "std::string") {
        char buffer[256];
        memset(buffer, 0, sizeof(buffer));
        auto& value = *(std::string*)((size_t)&Component+ Offset + field.Offset);
        strcpy(buffer, value.c_str());
        if (ImGui::InputText(fieldName.c_str(), buffer, sizeof(buffer))) {
            value = std::string(buffer);
        }
    }
    else if (field.Type == "bool") {
        bool location[1];
        auto& value = *(bool*)((size_t)&Component + Offset + field.Offset);
        memcpy(location, &value, sizeof(location));
        if (ImGui::Checkbox(fieldName.c_str(), location)) {
            memcpy(&value, location, sizeof(location));
        }
    }
    else if (field.Type == "float") {
        float location[1];
        auto& value = *(float*)((size_t)&Component + Offset + field.Offset);
        memcpy(location, &value, sizeof(location));
        if (ImGui::DragFloat(fieldName.c_str(), location, 0.1f, 0.0f, 0.0f, "%.1f"))
        {
            memcpy(&value, location, sizeof(location));
        }
    }
}




wierd class cast using dynamic proxy and java17 - java modules exception

I have trying to make a simple dynamic proxy example work without success. I assume I am doing something wrong but reading some java modules tuts and proxy tuts did not help me understand... the code is super basic with an interface, its implementation, an invocatgion handler and the class with the main method that runs the example:

package com.example.proxy;

public interface Foo {
    public String foo(String s);
}

package com.example.proxy;

    public class FooImpl implements Foo{
    
        @Override
        public String foo(String str) {
            return str;
        }
        
    }

package com.example.proxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

public class FooProxy implements InvocationHandler{
    Foo foo;
    public FooProxy(Foo s){
        this.foo = s;
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("%#$%#5^$@5^$@5^   PROXY");
        return ("[proxied] "+method.invoke(this.foo, args));
    }
    
}

package com.example.proxy;


import java.lang.reflect.Proxy;


public class MainTest {

    public static void main(String [] args){
        Foo o = (Foo)Proxy.newProxyInstance(Foo.class.getClassLoader(), Foo.class.getInterfaces(), new FooProxy(new FooImpl() ));

    }
}

running maintest yeilds:

Exception in thread "main" java.lang.ClassCastException: class jdk.proxy1.$Proxy0 cannot be cast to class com.example.proxy.Foo (jdk.proxy1.$Proxy0 is in module jdk.proxy1 of loader 'app'; com.example.proxy.Foo is in unnamed module of loader 'app')
    at com.example.proxy.MainTest.main(MainTest.java:10)

I tried exposing com.example.proxy in module-info.java explicitly, trying some opens etc but nothing helped so I assume I do not get something. can anyone help point me in the right direction thanks!