1 /*
2  * Copyright (c) 2003-2005 Sun Microsystems, Inc. All Rights Reserved.
3  * Copyright (c) 2013 JogAmp Community. All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions are
7  * met:
8  *
9  * - Redistribution of source code must retain the above copyright
10  *   notice, this list of conditions and the following disclaimer.
11  *
12  * - Redistribution in binary form must reproduce the above copyright
13  *   notice, this list of conditions and the following disclaimer in the
14  *   documentation and/or other materials provided with the distribution.
15  *
16  * Neither the name of Sun Microsystems, Inc. or the names of
17  * contributors may be used to endorse or promote products derived from
18  * this software without specific prior written permission.
19  *
20  * This software is provided "AS IS," without a warranty of any kind. ALL
21  * EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES,
22  * INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A
23  * PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN
24  * MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL NOT BE LIABLE FOR
25  * ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING OR
26  * DISTRIBUTING THIS SOFTWARE OR ITS DERIVATIVES. IN NO EVENT WILL SUN OR
27  * ITS LICENSORS BE LIABLE FOR ANY LOST REVENUE, PROFIT OR DATA, OR FOR
28  * DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE
29  * DAMAGES, HOWEVER CAUSED AND REGARDLESS OF THE THEORY OF LIABILITY,
30  * ARISING OUT OF THE USE OF OR INABILITY TO USE THIS SOFTWARE, EVEN IF
31  * SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
32  *
33  * You acknowledge that this software is not designed or intended for use
34  * in the design, construction, operation or maintenance of any nuclear
35  * facility.
36  */
37 package com.jogamp.gluegen.runtime;
38 
39 import com.jogamp.common.os.DynamicLookupHelper;
40 import com.jogamp.common.util.SecurityUtil;
41 
42 import java.io.BufferedOutputStream;
43 import java.io.File;
44 import java.io.FileOutputStream;
45 import java.io.IOException;
46 import java.io.PrintStream;
47 import java.lang.reflect.AccessibleObject;
48 import java.lang.reflect.Field;
49 import java.security.AccessController;
50 import java.security.PrivilegedAction;
51 import java.util.Iterator;
52 import java.util.LinkedHashSet;
53 import java.util.Map;
54 import java.util.Set;
55 import java.util.SortedMap;
56 import java.util.TreeMap;
57 
58 /**
59  * Superclass for all generated ProcAddressTables.
60  *
61  * A ProcAddressTable is a cache of pointers to the dynamically-linkable C
62  * functions this autogenerated Java binding has exposed. Some
63  * libraries such as OpenGL, OpenAL and others define function pointer
64  * signatures rather than statically linkable entry points for the
65  * purposes of being able to query at run-time whether a particular
66  * extension is available. This table acts as a cache of these
67  * function pointers. Each function pointer is typically looked up at
68  * run-time by a platform-dependent mechanism such as dlsym(),
69  * wgl/glXGetProcAddress(), or alGetProcAddress(). If the field containing the function
70  * pointer is 0, the function is considered to be unavailable and can
71  * not be called.
72  *
73  * @author Kenneth Russel
74  * @author Michael Bien
75  * @author Sven Gothel
76  *
77  * @see FunctionAddressResolver
78  * @see DynamicLookupHelper
79  */
80 public abstract class ProcAddressTable {
81 
82     private static final String PROCADDRESS_VAR_PREFIX = "_addressof_";
83     private static final int PROCADDRESS_VAR_PREFIX_LEN = PROCADDRESS_VAR_PREFIX.length();
84 
85     protected static boolean DEBUG;
86     protected static String DEBUG_PREFIX;
87     protected static int debugNum;
88 
89     private final FunctionAddressResolver resolver;
90 
91     static {
AccessController.doPrivileged(new PrivilegedAction<Object>() { @Override public Object run() { DEBUG = (System.getProperty(R) != null); if (DEBUG) { DEBUG_PREFIX = System.getProperty(R); } return null; } })92         AccessController.doPrivileged(new PrivilegedAction<Object>() {
93             @Override
94             public Object run() {
95                 DEBUG = (System.getProperty("jogamp.debug.ProcAddressHelper") != null);
96                 if (DEBUG) {
97                     DEBUG_PREFIX = System.getProperty("jogamp.debug.ProcAddressHelper.prefix");
98                 }
99                 return null;
100             }
101         });
102     }
103 
ProcAddressTable()104     public ProcAddressTable() {
105         this(new One2OneResolver());
106     }
107 
ProcAddressTable(final FunctionAddressResolver resolver)108     public ProcAddressTable(final FunctionAddressResolver resolver) {
109         this.resolver = resolver;
110     }
111 
112 
113     /**
114      * Resets the complete table.
115      * <p>
116      * If a {@link SecurityManager} is installed, user needs link permissions
117      * for <b>all</b> libraries, i.e. for <code>new RuntimePermission("loadLibrary.*");</code>!
118      * </p>
119      * @throws SecurityException if user is not granted access for all libraries.
120      */
reset(final DynamicLookupHelper lookup)121     public void reset(final DynamicLookupHelper lookup) throws SecurityException, RuntimeException {
122         if(null==lookup) {
123             throw new RuntimeException("Passed null DynamicLookupHelper");
124         }
125 
126         final Field[] fields = getClass().getDeclaredFields();
127 
128         final PrintStream dout;
129         if (DEBUG) {
130             dout = getDebugOutStream();
131             dout.println(getClass().getName()+".reset() (w/ "+fields.length+" prospective fields)");
132         } else {
133             dout = null;
134         }
135 
136         // All at once - performance.
137         AccessibleObject.setAccessible(fields, true);
138         lookup.claimAllLinkPermission();
139         try {
140             for (int i = 0; i < fields.length; ++i) {
141                 final String fieldName = fields[i].getName();
142                 if ( isAddressField(fieldName) ) {
143                     final String funcName = fieldToFunctionName(fieldName);
144                     setEntry(fields[i], funcName, lookup);
145                 }
146             }
147         } finally {
148             lookup.releaseAllLinkPermission();
149         }
150 
151         if (DEBUG) {
152             dout.flush();
153             if (DEBUG_PREFIX != null) {
154                 dout.close();
155             }
156         }
157     }
158 
159     /**
160      * Initializes the mapping for a single function.
161      * <p>
162      * If a {@link SecurityManager} is installed, user needs link permissions
163      * for <b>all</b> libraries, i.e. for <code>new RuntimePermission("loadLibrary.*");</code>!
164      * </p>
165      *
166      * @throws IllegalArgumentException if this function is not in this table.
167      * @throws SecurityException if user is not granted access for all libraries.
168      */
initEntry(final String name, final DynamicLookupHelper lookup)169     public void initEntry(final String name, final DynamicLookupHelper lookup) throws SecurityException, IllegalArgumentException {
170         final Field addressField = fieldForFunction(name);
171         addressField.setAccessible(true);
172         setEntry(addressField, name, lookup);
173     }
174 
setEntry(final Field addressField, final String funcName, final DynamicLookupHelper lookup)175     private final void setEntry(final Field addressField, final String funcName, final DynamicLookupHelper lookup) throws SecurityException {
176         try {
177             assert (addressField.getType() == Long.TYPE);
178             final long newProcAddress = resolver.resolve(funcName, lookup); // issues SecurityUtil.checkLinkPermission(String)
179             addressField.setLong(this, newProcAddress);
180             if (DEBUG) {
181                 getDebugOutStream().println("  " + addressField.getName() + " -> 0x" + Long.toHexString(newProcAddress));
182             }
183         } catch (final Exception e) {
184             throw new RuntimeException("Can not get proc address for method \""
185                     + funcName + "\": Couldn't set value of field \"" + addressField, e);
186         }
187     }
188 
fieldToFunctionName(final String addressFieldName)189     private final String fieldToFunctionName(final String addressFieldName) {
190         return addressFieldName.substring(PROCADDRESS_VAR_PREFIX_LEN);
191     }
192 
fieldForFunction(final String name)193     private final Field fieldForFunction(final String name) throws IllegalArgumentException {
194         try {
195             return getClass().getDeclaredField(PROCADDRESS_VAR_PREFIX + name);
196         } catch (final NoSuchFieldException ex) {
197             throw new IllegalArgumentException(getClass().getName() +" has no entry for the function '"+name+"'.", ex);
198         }
199     }
200 
201     /**
202      * Warning: Returns an accessible probably protected field!
203      * <p>
204      * Caller should have checked link permissions
205      * for <b>all</b> libraries, i.e. for <code>new RuntimePermission("loadLibrary.*");</code>
206      * <i>if</i> exposing the field or address!
207      * </p>
208      */
fieldForFunctionInSec(final String name)209     private final Field fieldForFunctionInSec(final String name) throws IllegalArgumentException {
210         return AccessController.doPrivileged(new PrivilegedAction<Field>() {
211             @Override
212             public Field run() {
213                 try {
214                     final Field addressField = ProcAddressTable.this.getClass().getDeclaredField(PROCADDRESS_VAR_PREFIX + name);
215                     addressField.setAccessible(true); // we need to read the protected value!
216                     return addressField;
217                 } catch (final NoSuchFieldException ex) {
218                     throw new IllegalArgumentException(getClass().getName() +" has no entry for the function '"+name+"'.", ex);
219                 }
220             }
221         } );
222     }
223 
224     private final boolean isAddressField(final String fieldName) {
225         return fieldName.startsWith(PROCADDRESS_VAR_PREFIX);
226     }
227 
228     private final static PrintStream getDebugOutStream() {
229         PrintStream out = null;
230         if (DEBUG) {
231             if (DEBUG_PREFIX != null) {
232                 try {
233                     out = new PrintStream(new BufferedOutputStream(new FileOutputStream(DEBUG_PREFIX + File.separatorChar
234                             + "procaddresstable-" + (++debugNum) + ".txt")));
235                 } catch (final IOException e) {
236                     e.printStackTrace();
237                     out = System.err;
238                 }
239             } else {
240                 out = System.err;
241             }
242         }
243         return out;
244     }
245 
246     /**
247      * Returns this table as map with the function name as key and the address as value.
248      */
249     private final Map<String, Long> toMap() {
250         final SortedMap<String, Long> map = new TreeMap<String, Long>();
251 
252         final Field[] fields = getClass().getFields();
253         try {
254             for (int i = 0; i < fields.length; ++i) {
255                 final String addressFieldName = fields[i].getName();
256                 if (isAddressField(addressFieldName)) {
257                     map.put(fieldToFunctionName(addressFieldName), (Long)fields[i].get(this));
258                 }
259             }
260         } catch (final IllegalArgumentException ex) {
261             throw new RuntimeException(ex);
262         } catch (final IllegalAccessException ex) {
263             throw new RuntimeException(ex);
264         }
265 
266         return map;
267     }
268 
269     /**
270      * Returns true only if non null function pointer to this function exists.
271      */
272     public final boolean isFunctionAvailable(final String functionName) {
273         try{
274             return isFunctionAvailableImpl(functionName);
275         } catch (final IllegalArgumentException ex) {
276             return false;
277         }
278     }
279 
280     /**
281      * This is a convenience method to query the native function existence by name.
282      * <p>
283      * It lets you avoid having to
284      * manually compute the &quot;{@link #PROCADDRESS_VAR_PREFIX} + &lt;functionName&gt;&quot;
285      * member variable name and look it up via reflection.
286      * </p>
287      *
288      * @throws IllegalArgumentException if this function is not in this table.
289      */
290     protected boolean isFunctionAvailableImpl(final String functionName) throws IllegalArgumentException {
291         final Field addressField = fieldForFunctionInSec(functionName);
292         try {
293             return 0 != addressField.getLong(this);
294         } catch (final IllegalAccessException ex) {
295             throw new RuntimeException(ex);
296         }
297     }
298 
299     /**
300      * This is a convenience method to query the native function handle by name.
301      * <p>
302      * It lets you avoid having to
303      * manually compute the &quot;{@link #PROCADDRESS_VAR_PREFIX} + &lt;functionName&gt;&quot;
304      * member variable name and look it up via reflection.
305      * </p>
306      * <p>
307      * If a {@link SecurityManager} is installed, user needs link permissions
308      * for <b>all</b> libraries, i.e. for <code>new RuntimePermission("loadLibrary.*");</code>!
309      * </p>
310      *
311      * @throws IllegalArgumentException if this function is not in this table.
312      * @throws SecurityException if user is not granted access for all libraries.
313      */
314     public long getAddressFor(final String functionName) throws SecurityException, IllegalArgumentException {
315         SecurityUtil.checkAllLinkPermission();
316         final Field addressField = fieldForFunctionInSec(functionName);
317         try {
318             return addressField.getLong(this);
319         } catch (final IllegalAccessException ex) {
320             throw new RuntimeException(ex);
321         }
322     }
323 
324     /**
325      * Returns all functions pointing to null.
326      */
327     public final Set<String> getNullPointerFunctions() {
328         final Map<String, Long> table = toMap();
329         final Set<String> nullPointers = new LinkedHashSet<String>();
330         for (final Iterator<Map.Entry<String, Long>> it = table.entrySet().iterator(); it.hasNext();) {
331             final Map.Entry<String, Long> entry = it.next();
332             final long address = entry.getValue().longValue();
333             if(address == 0) {
334                 nullPointers.add(entry.getKey());
335             }
336         }
337         return nullPointers;
338     }
339 
340     @Override
341     public final String toString() {
342         return getClass().getName()+""+toMap();
343     }
344 
345     private static class One2OneResolver implements FunctionAddressResolver {
346         @Override
347         public long resolve(final String name, final DynamicLookupHelper lookup) throws SecurityException {
348             return lookup.dynamicLookupFunction(name);
349         }
350     }
351 
352 
353 }
354