mirror of
https://github.com/i-net-software/JWebAssembly.git
synced 2025-03-15 02:44:47 +01:00
call class initializer before access to static fields
This commit is contained in:
parent
710127bb44
commit
69b8db16d1
@ -15,6 +15,8 @@
|
||||
*/
|
||||
package de.inetsoftware.jwebassembly.module;
|
||||
|
||||
import static de.inetsoftware.jwebassembly.module.WasmCodeBuilder.CLASS_INIT;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
@ -42,10 +44,10 @@ class FunctionManager {
|
||||
|
||||
private final Set<String> usedClasses = new LinkedHashSet<>();
|
||||
|
||||
private boolean isFinish;
|
||||
private boolean isFinish;
|
||||
|
||||
/**
|
||||
* Finish the prepare. Now no new function should be added.
|
||||
* Finish the prepare. Now no new function should be added.
|
||||
*/
|
||||
void prepareFinish() {
|
||||
isFinish = true;
|
||||
@ -108,8 +110,8 @@ class FunctionManager {
|
||||
* @param importAnannotation
|
||||
* the annotation of the import
|
||||
*/
|
||||
void markAsImport( @Nonnull FunctionName name, Map<String,Object> importAnannotation ) {
|
||||
markAsImport( name, (key) -> importAnannotation.get( key ) );
|
||||
void markAsImport( @Nonnull FunctionName name, Map<String, Object> importAnannotation ) {
|
||||
markAsImport( name, ( key ) -> importAnannotation.get( key ) );
|
||||
}
|
||||
|
||||
/**
|
||||
@ -121,7 +123,7 @@ class FunctionManager {
|
||||
* @param importAnannotation
|
||||
* the annotation of the import
|
||||
*/
|
||||
void markAsImport( @Nonnull FunctionName name, Function<String,Object> importAnannotation ) {
|
||||
void markAsImport( @Nonnull FunctionName name, Function<String, Object> importAnannotation ) {
|
||||
getOrCreate( name ).importAnannotation = importAnannotation;
|
||||
}
|
||||
|
||||
@ -260,7 +262,7 @@ class FunctionManager {
|
||||
*/
|
||||
@Nonnull
|
||||
Iterator<FunctionName> getWriteLaterClinit() {
|
||||
return iterator( entry -> entry.getKey().methodName.equals( "<clinit>" ) && entry.getValue().state != State.None );
|
||||
return iterator( entry -> entry.getKey().methodName.equals( CLASS_INIT ) && entry.getValue().state != State.None );
|
||||
}
|
||||
|
||||
/**
|
||||
@ -299,7 +301,9 @@ class FunctionManager {
|
||||
|
||||
/**
|
||||
* get a iterator for function names
|
||||
* @param filter the filter
|
||||
*
|
||||
* @param filter
|
||||
* the filter
|
||||
* @return the iterator
|
||||
*/
|
||||
@Nonnull
|
||||
@ -448,7 +452,7 @@ class FunctionManager {
|
||||
*
|
||||
* @param name
|
||||
* the name
|
||||
* @return the index in the itable
|
||||
* @return the index in the itable
|
||||
*/
|
||||
int getITableIndex( @Nonnull FunctionName name ) {
|
||||
return getOrCreate( name ).itableIdx;
|
||||
@ -458,7 +462,7 @@ class FunctionManager {
|
||||
* State of a function/method
|
||||
*/
|
||||
private static class FunctionState {
|
||||
private State state = State.None;
|
||||
private State state = State.None;
|
||||
|
||||
private MethodInfo method;
|
||||
|
||||
|
@ -85,13 +85,13 @@ class JavaMethodWasmCodeBuilder extends WasmCodeBuilder {
|
||||
reset( code.getLocalVariableTable(), method, null );
|
||||
branchManager.reset( code );
|
||||
|
||||
if( "<clinit>".equals( method.getName() ) ) {
|
||||
if( CLASS_INIT.equals( method.getName() ) ) {
|
||||
// Add a hook to run the class/static constructor only once
|
||||
FunctionName name = new FunctionName( method.getClassName(), "<clisinit>", "" );
|
||||
addGlobalInstruction( true, name, ValueType.i32, -1, -1 );
|
||||
FunctionName name = new FunctionName( method.getClassName(), "<class_isInit>", "" );
|
||||
addGlobalInstruction( true, name, ValueType.i32, null, -1, -1 );
|
||||
addBlockInstruction( WasmBlockOperator.BR_IF, 0, -1, -1 );
|
||||
addConstInstruction( 1, ValueType.i32, -1, -1 );
|
||||
addGlobalInstruction( false, name, ValueType.i32, -1, -1 );
|
||||
addGlobalInstruction( false, name, ValueType.i32, null, -1, -1 );
|
||||
}
|
||||
|
||||
byteCode = code.getByteCode();
|
||||
|
@ -15,6 +15,8 @@
|
||||
*/
|
||||
package de.inetsoftware.jwebassembly.module;
|
||||
|
||||
import static de.inetsoftware.jwebassembly.module.WasmCodeBuilder.CLASS_INIT;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
@ -387,7 +389,7 @@ public class ModuleGenerator {
|
||||
String className = iterator.next();
|
||||
ClassFile classFile = classFileLoader.get( className );
|
||||
if( classFile != null ) {
|
||||
MethodInfo method = classFile.getMethod( "<clinit>", "()V" );
|
||||
MethodInfo method = classFile.getMethod( CLASS_INIT, "()V" );
|
||||
if( method != null ) {
|
||||
functions.markAsNeeded( new FunctionName( method ), false );
|
||||
}
|
||||
|
@ -16,6 +16,8 @@
|
||||
*/
|
||||
package de.inetsoftware.jwebassembly.module;
|
||||
|
||||
import static de.inetsoftware.jwebassembly.module.WasmCodeBuilder.CONSTRUCTOR;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.UncheckedIOException;
|
||||
@ -58,38 +60,39 @@ import de.inetsoftware.jwebassembly.watparser.WatParser;
|
||||
*/
|
||||
public class TypeManager {
|
||||
|
||||
/** name of virtual function table, start with a point for an invalid Java identifier */
|
||||
static final String FIELD_VTABLE = ".vtable";
|
||||
/** name of virtual function table, start with a point for an invalid Java identifier */
|
||||
static final String FIELD_VTABLE = ".vtable";
|
||||
|
||||
/**
|
||||
* Name of field with system hash code, start with a point for an invalid Java identifier.
|
||||
*/
|
||||
static final String FIELD_HASHCODE = ".hashcode";
|
||||
static final String FIELD_HASHCODE = ".hashcode";
|
||||
|
||||
/**
|
||||
* Name of field with array value.
|
||||
*/
|
||||
public static final String FIELD_VALUE = ".array";
|
||||
public static final String FIELD_VALUE = ".array";
|
||||
|
||||
/**
|
||||
* Byte position in the type description that contains the offset to the interfaces. Length 4 bytes.
|
||||
*/
|
||||
public static final int TYPE_DESCRIPTION_INTERFACE_OFFSET = 0;
|
||||
public static final int TYPE_DESCRIPTION_INTERFACE_OFFSET = 0;
|
||||
|
||||
/**
|
||||
* Byte position in the type description that contains the offset to the instanceof list. Length 4 bytes.
|
||||
*/
|
||||
public static final int TYPE_DESCRIPTION_INSTANCEOF_OFFSET = 4;
|
||||
public static final int TYPE_DESCRIPTION_INSTANCEOF_OFFSET = 4;
|
||||
|
||||
/**
|
||||
* Byte position in the type description that contains the offset to class name idx in the string constant table. Length 4 bytes.
|
||||
* Byte position in the type description that contains the offset to class name idx in the string constant table.
|
||||
* Length 4 bytes.
|
||||
*/
|
||||
public static final int TYPE_DESCRIPTION_TYPE_NAME = 8;
|
||||
public static final int TYPE_DESCRIPTION_TYPE_NAME = 8;
|
||||
|
||||
/**
|
||||
* Byte position in the type description that contains the type of the array (component type). Length 4 bytes.
|
||||
*/
|
||||
public static final int TYPE_DESCRIPTION_ARRAY_TYPE = 12;
|
||||
public static final int TYPE_DESCRIPTION_ARRAY_TYPE = 12;
|
||||
|
||||
/**
|
||||
* The reserved position on start of the vtable:
|
||||
@ -97,73 +100,74 @@ public class TypeManager {
|
||||
* <li>offset of instanceof list
|
||||
* <li>offset of class name idx in the string constant table
|
||||
*/
|
||||
private static final int VTABLE_FIRST_FUNCTION_INDEX = 4;
|
||||
private static final int VTABLE_FIRST_FUNCTION_INDEX = 4;
|
||||
|
||||
private static final FunctionName CLASS_CONSTANT_FUNCTION = new FunctionName( "java/lang/Class.classConstant(I)Ljava/lang/Class;" );
|
||||
private static final FunctionName CLASS_CONSTANT_FUNCTION = new FunctionName( "java/lang/Class.classConstant(I)Ljava/lang/Class;" );
|
||||
|
||||
/**
|
||||
* Type id of primitive class
|
||||
*/
|
||||
public static final int BOOLEAN = 0;
|
||||
public static final int BOOLEAN = 0;
|
||||
|
||||
/**
|
||||
* Type id of primitive class
|
||||
*/
|
||||
public static final int BYTE = 1;
|
||||
public static final int BYTE = 1;
|
||||
|
||||
/**
|
||||
* Type id of primitive class
|
||||
*/
|
||||
public static final int CHAR = 2;
|
||||
public static final int CHAR = 2;
|
||||
|
||||
/**
|
||||
* Type id of primitive class
|
||||
*/
|
||||
public static final int DOUBLE = 3;
|
||||
public static final int DOUBLE = 3;
|
||||
|
||||
/**
|
||||
* Type id of primitive class
|
||||
*/
|
||||
public static final int FLOAT = 4;
|
||||
public static final int FLOAT = 4;
|
||||
|
||||
/**
|
||||
* Type id of primitive class
|
||||
*/
|
||||
public static final int INT = 5;
|
||||
public static final int INT = 5;
|
||||
|
||||
/**
|
||||
* Type id of primitive class
|
||||
*/
|
||||
public static final int LONG = 6;
|
||||
public static final int LONG = 6;
|
||||
|
||||
/**
|
||||
* Type id of primitive class
|
||||
*/
|
||||
public static final int SHORT = 7;
|
||||
public static final int SHORT = 7;
|
||||
|
||||
/**
|
||||
* Type id of primitive class
|
||||
*/
|
||||
public static final int VOID = 8;
|
||||
public static final int VOID = 8;
|
||||
|
||||
/**
|
||||
* the list of primitive types. The order is important and must correlate with getPrimitiveClass.
|
||||
*
|
||||
* @see ReplacementForClass#getPrimitiveClass(String)
|
||||
*/
|
||||
private static final String[] PRIMITIVE_CLASSES = { "boolean", "byte", "char", "double", "float", "int", "long", "short", "void" };
|
||||
private static final String[] PRIMITIVE_CLASSES =
|
||||
{ "boolean", "byte", "char", "double", "float", "int", "long", "short", "void" };
|
||||
|
||||
private final Map<Object, StructType> structTypes = new LinkedHashMap<>();
|
||||
private final Map<Object, StructType> structTypes = new LinkedHashMap<>();
|
||||
|
||||
private final Map<BlockType, BlockType> blockTypes = new LinkedHashMap<>();
|
||||
private final Map<BlockType, BlockType> blockTypes = new LinkedHashMap<>();
|
||||
|
||||
private int typeIndexCounter;
|
||||
private int typeIndexCounter;
|
||||
|
||||
private boolean isFinish;
|
||||
private boolean isFinish;
|
||||
|
||||
final WasmOptions options;
|
||||
final WasmOptions options;
|
||||
|
||||
private int typeTableOffset;
|
||||
private int typeTableOffset;
|
||||
|
||||
private ClassFileLoader classFileLoader;
|
||||
|
||||
@ -179,7 +183,9 @@ public class TypeManager {
|
||||
|
||||
/**
|
||||
* Initialize the type manager
|
||||
* @param classFileLoader for loading the class files
|
||||
*
|
||||
* @param classFileLoader
|
||||
* for loading the class files
|
||||
*/
|
||||
void init( ClassFileLoader classFileLoader ) {
|
||||
this.classFileLoader = classFileLoader;
|
||||
@ -260,6 +266,7 @@ public class TypeManager {
|
||||
|
||||
/**
|
||||
* Get the function name to get a constant class.
|
||||
*
|
||||
* @return the function
|
||||
*/
|
||||
@Nonnull
|
||||
@ -424,8 +431,8 @@ public class TypeManager {
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the FunctionName for a virtual call. The function has 2 parameters (THIS,
|
||||
* virtualfunctionIndex) and returns the index of the function.
|
||||
* Create the FunctionName for a virtual call. The function has 2 parameters (THIS, virtualfunctionIndex) and
|
||||
* returns the index of the function.
|
||||
*
|
||||
* @return the name
|
||||
*/
|
||||
@ -453,7 +460,7 @@ public class TypeManager {
|
||||
static int callInterface( OBJECT THIS, int classIndex, int virtualfunctionIndex ) {
|
||||
int table = THIS.vtable;
|
||||
table += i32_load[table];
|
||||
|
||||
|
||||
do {
|
||||
int nextClass = i32_load[table];
|
||||
if( nextClass == classIndex ) {
|
||||
@ -476,8 +483,7 @@ public class TypeManager {
|
||||
+ "local.set 3 " // save $table, the itable start location
|
||||
+ "loop" //
|
||||
+ " local.get 3" // get $table
|
||||
+ " i32.load offset=0 align=4"
|
||||
+ " local.tee 4" // save $nextClass
|
||||
+ " i32.load offset=0 align=4" + " local.tee 4" // save $nextClass
|
||||
+ " local.get 1" // get $classIndex
|
||||
+ " i32.eq" //
|
||||
+ " if" // $nextClass == $classIndex
|
||||
@ -587,30 +593,30 @@ public class TypeManager {
|
||||
*/
|
||||
public static class StructType implements AnyType {
|
||||
|
||||
private final String name;
|
||||
private final String name;
|
||||
|
||||
private final StructTypeKind kind;
|
||||
private final StructTypeKind kind;
|
||||
|
||||
private final TypeManager manager;
|
||||
private final TypeManager manager;
|
||||
|
||||
private final int classIndex;
|
||||
private final int classIndex;
|
||||
|
||||
private int code = Integer.MAX_VALUE;
|
||||
private int code = Integer.MAX_VALUE;
|
||||
|
||||
private HashSet<String> neededFields = new HashSet<>();
|
||||
private HashSet<String> neededFields = new HashSet<>();
|
||||
|
||||
private List<NamedStorageType> fields;
|
||||
private List<NamedStorageType> fields;
|
||||
|
||||
private List<FunctionName> vtable;
|
||||
private List<FunctionName> vtable;
|
||||
|
||||
private Set<StructType> instanceOFs;
|
||||
private Set<StructType> instanceOFs;
|
||||
|
||||
private Map<StructType,List<FunctionName>> interfaceMethods;
|
||||
private Map<StructType, List<FunctionName>> interfaceMethods;
|
||||
|
||||
/**
|
||||
* The offset to the vtable in the data section.
|
||||
*/
|
||||
private int vtableOffset;
|
||||
private int vtableOffset;
|
||||
|
||||
/**
|
||||
* Create a reference to type
|
||||
@ -770,7 +776,7 @@ public class TypeManager {
|
||||
|
||||
// calculate the vtable (the function indexes of the virtual methods)
|
||||
for( MethodInfo method : classFile.getMethods() ) {
|
||||
if( method.isStatic() || "<init>".equals( method.getName() ) ) {
|
||||
if( method.isStatic() || CONSTRUCTOR.equals( method.getName() ) ) {
|
||||
continue;
|
||||
}
|
||||
FunctionName funcName = new FunctionName( method );
|
||||
@ -1017,6 +1023,7 @@ public class TypeManager {
|
||||
|
||||
/**
|
||||
* Get kind of the StructType
|
||||
*
|
||||
* @return the type kind
|
||||
*/
|
||||
public StructTypeKind getKind() {
|
||||
@ -1025,6 +1032,7 @@ public class TypeManager {
|
||||
|
||||
/**
|
||||
* Get the name of the Java type
|
||||
*
|
||||
* @return the name
|
||||
*/
|
||||
public String getName() {
|
||||
@ -1051,6 +1059,7 @@ public class TypeManager {
|
||||
|
||||
/**
|
||||
* Get the fields of this struct
|
||||
*
|
||||
* @return the fields
|
||||
*/
|
||||
public List<NamedStorageType> getFields() {
|
||||
@ -1189,7 +1198,7 @@ public class TypeManager {
|
||||
super( name, StructTypeKind.lambda, manager );
|
||||
this.paramFields = new ArrayList<>( params.size() );
|
||||
for( int i = 0; i < params.size(); i++ ) {
|
||||
paramFields.add( new NamedStorageType( params.get( i ), "", "arg$" + (i+1) ) );
|
||||
paramFields.add( new NamedStorageType( params.get( i ), "", "arg$" + (i + 1) ) );
|
||||
}
|
||||
this.interfaceType = interfaceType;
|
||||
this.interfaceMethodName = interfaceMethodName;
|
||||
@ -1199,10 +1208,12 @@ public class TypeManager {
|
||||
protected boolean hasWasmCode() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean istStatic() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected WasmCodeBuilder getCodeBuilder( WatParser watParser ) {
|
||||
WasmCodeBuilder codebuilder = watParser;
|
||||
@ -1290,12 +1301,15 @@ public class TypeManager {
|
||||
|
||||
@Nonnull
|
||||
private final List<AnyType> params;
|
||||
|
||||
@Nonnull
|
||||
private final List<AnyType> results;
|
||||
private int code;
|
||||
private String name;
|
||||
|
||||
public BlockType(List<AnyType> params, List<AnyType> results) {
|
||||
private int code;
|
||||
|
||||
private String name;
|
||||
|
||||
public BlockType( List<AnyType> params, List<AnyType> results ) {
|
||||
this.params = params;
|
||||
this.results = results;
|
||||
}
|
||||
|
@ -57,6 +57,12 @@ import de.inetsoftware.jwebassembly.wasm.WasmBlockOperator;
|
||||
*/
|
||||
public abstract class WasmCodeBuilder {
|
||||
|
||||
/** Java method name of of constructor */
|
||||
static final String CONSTRUCTOR = "<init>";
|
||||
|
||||
/** Java method name of static constructor or initialization method */
|
||||
static final String CLASS_INIT = "<clinit>";
|
||||
|
||||
private final LocaleVariableManager localVariables;
|
||||
|
||||
private final List<WasmInstruction> instructions;
|
||||
@ -497,7 +503,16 @@ public abstract class WasmCodeBuilder {
|
||||
protected void addGlobalInstruction( boolean load, Member ref, int javaCodePos, int lineNumber ) {
|
||||
FunctionName name = new FunctionName( ref );
|
||||
AnyType type = new ValueTypeParser( ref.getType(), types ).next();
|
||||
addGlobalInstruction( load, name, type, javaCodePos, lineNumber );
|
||||
FunctionName clinit;
|
||||
if( load ) {
|
||||
clinit = new FunctionName( name.className, CLASS_INIT, "()V" );
|
||||
if( !functions.isUsed( clinit ) ) {
|
||||
clinit = null;
|
||||
}
|
||||
} else {
|
||||
clinit = null;
|
||||
}
|
||||
addGlobalInstruction( load, name, type, clinit, javaCodePos, lineNumber );
|
||||
}
|
||||
|
||||
/**
|
||||
@ -514,8 +529,8 @@ public abstract class WasmCodeBuilder {
|
||||
* @param lineNumber
|
||||
* the line number in the Java source code
|
||||
*/
|
||||
protected void addGlobalInstruction( boolean load, FunctionName name, AnyType type, int javaCodePos, int lineNumber ) {
|
||||
instructions.add( new WasmGlobalInstruction( load, name, type, javaCodePos, lineNumber ) );
|
||||
protected void addGlobalInstruction( boolean load, FunctionName name, AnyType type, FunctionName clinit, int javaCodePos, int lineNumber ) {
|
||||
instructions.add( new WasmGlobalInstruction( load, name, type, clinit, javaCodePos, lineNumber ) );
|
||||
functions.markClassAsUsed( name.className );
|
||||
}
|
||||
|
||||
@ -655,7 +670,7 @@ public abstract class WasmCodeBuilder {
|
||||
name = functions.markAsNeeded( name, needThisParameter );
|
||||
WasmCallInstruction instruction = new WasmCallInstruction( name, javaCodePos, lineNumber, types, needThisParameter );
|
||||
|
||||
if( "<init>".equals( name.methodName ) ) {
|
||||
if( CONSTRUCTOR.equals( name.methodName ) ) {
|
||||
// check if there a factory for the constructor in JavaScript then we need to do some more complex patching
|
||||
Function<String, Object> importAnannotation = functions.getImportAnannotation( name );
|
||||
FunctionName factoryName = null;
|
||||
@ -667,7 +682,7 @@ public abstract class WasmCodeBuilder {
|
||||
factoryName = new ImportSyntheticFunctionName( "String", "init", signature, importAnannotation );
|
||||
} else {
|
||||
MethodInfo replace = functions.replace( name, null );
|
||||
if( replace != null && !"<init>".equals( replace.getName() ) ) {
|
||||
if( replace != null && !CONSTRUCTOR.equals( replace.getName() ) ) {
|
||||
// the constructor was replaced with a factory method. Typical this method called then a JavaScript replacement
|
||||
factoryName = new FunctionName( replace );
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright 2018 - 2021 Volker Berlin (i-net software)
|
||||
Copyright 2018 - 2022 Volker Berlin (i-net software)
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
@ -19,6 +19,7 @@ package de.inetsoftware.jwebassembly.module;
|
||||
import java.io.IOException;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import de.inetsoftware.jwebassembly.wasm.AnyType;
|
||||
|
||||
@ -30,11 +31,13 @@ import de.inetsoftware.jwebassembly.wasm.AnyType;
|
||||
*/
|
||||
class WasmGlobalInstruction extends WasmInstruction {
|
||||
|
||||
private boolean load;
|
||||
private boolean load;
|
||||
|
||||
private FunctionName name;
|
||||
|
||||
private AnyType type;
|
||||
private AnyType type;
|
||||
|
||||
private FunctionName clinit;
|
||||
|
||||
/**
|
||||
* Create an instance of a load/store instruction
|
||||
@ -45,16 +48,19 @@ class WasmGlobalInstruction extends WasmInstruction {
|
||||
* the name of the static field
|
||||
* @param type
|
||||
* the type of the static field
|
||||
* @param clinit
|
||||
* a reference to the class/static constructor which should executed before access a static field
|
||||
* @param javaCodePos
|
||||
* the code position/offset in the Java method
|
||||
* @param lineNumber
|
||||
* the line number in the Java source code
|
||||
*/
|
||||
WasmGlobalInstruction( boolean load, @Nonnull FunctionName name, AnyType type, int javaCodePos, int lineNumber ) {
|
||||
WasmGlobalInstruction( boolean load, @Nonnull FunctionName name, AnyType type, @Nullable FunctionName clinit, int javaCodePos, int lineNumber ) {
|
||||
super( javaCodePos, lineNumber );
|
||||
this.load = load;
|
||||
this.name = name;
|
||||
this.type = type;
|
||||
this.clinit = clinit;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -80,6 +86,9 @@ class WasmGlobalInstruction extends WasmInstruction {
|
||||
*/
|
||||
@Override
|
||||
public void writeTo( @Nonnull ModuleWriter writer ) throws IOException {
|
||||
if( clinit != null ) {
|
||||
writer.writeFunctionCall( clinit, clinit.signatureName );
|
||||
}
|
||||
writer.writeGlobalAccess( load, name, type );
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user