• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 package org.robolectric.res.android;
2 
3 // transliterated from https://android.googlesource.com/platform/frameworks/base/+/android-9.0.0_r12/libs/androidfw/ResourceTypes.cpp
4 //   and https://android.googlesource.com/platform/frameworks/base/+/android-9.0.0_r12/include/androidfw/ResourceTypes.h
5 
6 import static org.robolectric.res.android.Errors.BAD_TYPE;
7 import static org.robolectric.res.android.Errors.NAME_NOT_FOUND;
8 import static org.robolectric.res.android.Errors.NO_ERROR;
9 import static org.robolectric.res.android.Errors.NO_INIT;
10 import static org.robolectric.res.android.ResourceString.decodeString;
11 import static org.robolectric.res.android.ResourceTypes.validate_chunk;
12 import static org.robolectric.res.android.Util.ALOGI;
13 import static org.robolectric.res.android.Util.ALOGW;
14 import static org.robolectric.res.android.Util.SIZEOF_INT;
15 import static org.robolectric.res.android.Util.isTruthy;
16 
17 import java.lang.ref.WeakReference;
18 import java.nio.ByteBuffer;
19 import java.nio.ByteOrder;
20 import java.util.Objects;
21 import org.robolectric.res.android.ResourceTypes.ResChunk_header;
22 import org.robolectric.res.android.ResourceTypes.ResStringPool_header;
23 import org.robolectric.res.android.ResourceTypes.ResStringPool_header.Writer;
24 import org.robolectric.res.android.ResourceTypes.ResStringPool_ref;
25 import org.robolectric.res.android.ResourceTypes.ResStringPool_span;
26 import org.robolectric.res.android.ResourceTypes.WithOffset;
27 
28 /**
29  * Convenience class for accessing data in a ResStringPool resource.
30  */
31 @SuppressWarnings("NewApi")
32 public class ResStringPool {
33 
34   private static boolean kDebugStringPoolNoisy = false;
35 
36   private final long myNativePtr;
37 
38   private int                    mError;
39 
40    byte[]                       mOwnedData;
41   //private Object mOwnedData;
42 
43   private ResStringPool_header mHeader;
44   private int                      mSize;
45 //    private mutable Mutex               mDecodeLock;
46 //    const uint32_t*             mEntries;
47   private IntArray             mEntries;
48 //    const uint32_t*             mEntryStyles;
49     private IntArray             mEntryStyles;
50 //    const void*                 mStrings;
51     private int                 mStrings;
52   //private List<String> mStrings;
53   //private String[] mCache;
54   //private char16_t mutable**          mCache;
55     private int                    mStringPoolSize;    // number of uint16_t
56 //    const uint32_t*             mStyles;
57     private int             mStyles;
58     private int                    mStylePoolSize;    // number of int
59 
ResStringPool()60   public ResStringPool() {
61     mError = NO_INIT;
62     myNativePtr = Registries.NATIVE_STRING_POOLS.register(new WeakReference<>(this));
63   }
64 
65   @Override
finalize()66   protected void finalize() throws Throwable {
67     Registries.NATIVE_STRING_POOLS.unregister(myNativePtr);
68   }
69 
getNativePtr()70   public long getNativePtr() {
71     return myNativePtr;
72   }
73 
getNativeObject(long nativeId)74   public static ResStringPool getNativeObject(long nativeId) {
75     return Registries.NATIVE_STRING_POOLS.getNativeObject(nativeId).get();
76   }
77 
78   static class IntArray extends WithOffset {
IntArray(ByteBuffer buf, int offset)79     IntArray(ByteBuffer buf, int offset) {
80       super(buf, offset);
81     }
82 
get(int idx)83     int get(int idx) {
84       return myBuf().getInt(myOffset() + idx * SIZEOF_INT);
85     }
86   }
87 
setToEmpty()88   void setToEmpty()
89   {
90     uninit();
91 
92     ByteBuffer buf = ByteBuffer.allocate(16 * 1024).order(ByteOrder.LITTLE_ENDIAN);
93     Writer resStringPoolWriter = new Writer();
94     resStringPoolWriter.write(buf);
95     mOwnedData = new byte[buf.position()];
96     buf.position();
97     buf.get(mOwnedData);
98 
99     ResStringPool_header header = new ResStringPool_header(buf, 0);
100     mSize = 0;
101     mEntries = null;
102     mStrings = 0;
103     mStringPoolSize = 0;
104     mEntryStyles = null;
105     mStyles = 0;
106     mStylePoolSize = 0;
107     mHeader = header;
108   }
109 
110   //  status_t setTo(const void* data, size_t size, bool copyData=false);
setTo(ByteBuffer buf, int offset, int size, boolean copyData)111   public int setTo(ByteBuffer buf, int offset, int size, boolean copyData) {
112     if (!isTruthy(buf) || !isTruthy(size)) {
113       return (mError=BAD_TYPE);
114     }
115 
116     uninit();
117 
118     // The chunk must be at least the size of the string pool header.
119     if (size < ResStringPool_header.SIZEOF) {
120       ALOGW("Bad string block: data size %d is too small to be a string block", size);
121       return (mError=BAD_TYPE);
122     }
123 
124     // The data is at least as big as a ResChunk_header, so we can safely validate the other
125     // header fields.
126     // `data + size` is safe because the source of `size` comes from the kernel/filesystem.
127     if (validate_chunk(new ResChunk_header(buf, offset), ResStringPool_header.SIZEOF,
128         size,
129         "ResStringPool_header") != NO_ERROR) {
130       ALOGW("Bad string block: malformed block dimensions");
131       return (mError=BAD_TYPE);
132     }
133 
134 //    final boolean notDeviceEndian = htods((short) 0xf0) != 0xf0;
135 //
136 //    if (copyData || notDeviceEndian) {
137 //      mOwnedData = data;
138 //      if (mOwnedData == null) {
139 //        return (mError=NO_MEMORY);
140 //      }
141 ////      memcpy(mOwnedData, data, size);
142 //      data = mOwnedData;
143 //    }
144 
145     // The size has been checked, so it is safe to read the data in the ResStringPool_header
146     // data structure.
147     mHeader = new ResStringPool_header(buf, offset);
148 
149 //    if (notDeviceEndian) {
150 //      ResStringPool_header h = final_cast<ResStringPool_header*>(mHeader);
151 //      h.header.headerSize = dtohs(mHeader.header.headerSize);
152 //      h.header.type = dtohs(mHeader.header.type);
153 //      h.header.size = dtohl(mHeader.header.size);
154 //      h.stringCount = dtohl(mHeader.stringCount);
155 //      h.styleCount = dtohl(mHeader.styleCount);
156 //      h.flags = dtohl(mHeader.flags);
157 //      h.stringsStart = dtohl(mHeader.stringsStart);
158 //      h.stylesStart = dtohl(mHeader.stylesStart);
159 //    }
160 
161     if (mHeader.header.headerSize > mHeader.header.size
162         || mHeader.header.size > size) {
163       ALOGW("Bad string block: header size %d or total size %d is larger than data size %d\n",
164           (int)mHeader.header.headerSize, (int)mHeader.header.size, (int)size);
165       return (mError=BAD_TYPE);
166     }
167     mSize = mHeader.header.size;
168     mEntries = new IntArray(mHeader.myBuf(), mHeader.myOffset() + mHeader.header.headerSize);
169 
170     if (mHeader.stringCount > 0) {
171       if ((mHeader.stringCount*4 /*sizeof(uint32_t)*/ < mHeader.stringCount)  // uint32 overflow?
172           || (mHeader.header.headerSize+(mHeader.stringCount*4 /*sizeof(uint32_t)*/))
173           > size) {
174         ALOGW("Bad string block: entry of %d items extends past data size %d\n",
175             (int)(mHeader.header.headerSize+(mHeader.stringCount*4/*sizeof(uint32_t)*/)),
176             (int)size);
177         return (mError=BAD_TYPE);
178       }
179 
180       int charSize;
181       if (isTruthy(mHeader.flags & ResStringPool_header.UTF8_FLAG)) {
182         charSize = 1 /*sizeof(uint8_t)*/;
183       } else {
184         charSize = 2 /*sizeof(uint16_t)*/;
185       }
186 
187       // There should be at least space for the smallest string
188       // (2 bytes length, null terminator).
189       if (mHeader.stringsStart >= (mSize - 2 /*sizeof(uint16_t)*/)) {
190         ALOGW("Bad string block: string pool starts at %d, after total size %d\n",
191             (int)mHeader.stringsStart, (int)mHeader.header.size);
192         return (mError=BAD_TYPE);
193       }
194 
195       mStrings = mHeader.stringsStart;
196 
197       if (mHeader.styleCount == 0) {
198         mStringPoolSize = (mSize - mHeader.stringsStart) / charSize;
199       } else {
200         // check invariant: styles starts before end of data
201         if (mHeader.stylesStart >= (mSize - 2 /*sizeof(uint16_t)*/)) {
202           ALOGW("Bad style block: style block starts at %d past data size of %d\n",
203               (int)mHeader.stylesStart, (int)mHeader.header.size);
204           return (mError=BAD_TYPE);
205         }
206         // check invariant: styles follow the strings
207         if (mHeader.stylesStart <= mHeader.stringsStart) {
208           ALOGW("Bad style block: style block starts at %d, before strings at %d\n",
209               (int)mHeader.stylesStart, (int)mHeader.stringsStart);
210           return (mError=BAD_TYPE);
211         }
212         mStringPoolSize =
213             (mHeader.stylesStart-mHeader.stringsStart)/charSize;
214       }
215 
216       // check invariant: stringCount > 0 requires a string pool to exist
217       if (mStringPoolSize == 0) {
218         ALOGW("Bad string block: stringCount is %d but pool size is 0\n", (int)mHeader.stringCount);
219         return (mError=BAD_TYPE);
220       }
221 
222       //      if (notDeviceEndian) {
223       //        int i;
224       //        uint32_t* e = final_cast<uint32_t*>(mEntries);
225       //        for (i=0; i<mHeader.stringCount; i++) {
226       //          e[i] = dtohl(mEntries[i]);
227       //        }
228       //        if (!(mHeader.flags&ResStringPool_header::UTF8_FLAG)) {
229       //                final uint16_t* strings = (final uint16_t*)mStrings;
230       //          uint16_t* s = final_cast<uint16_t*>(strings);
231       //          for (i=0; i<mStringPoolSize; i++) {
232       //            s[i] = dtohs(strings[i]);
233       //          }
234       //        }
235       //      }
236 
237       //      if ((mHeader->flags&ResStringPool_header::UTF8_FLAG &&
238       //          ((uint8_t*)mStrings)[mStringPoolSize-1] != 0) ||
239       //      (!(mHeader->flags&ResStringPool_header::UTF8_FLAG) &&
240       //          ((uint16_t*)mStrings)[mStringPoolSize-1] != 0)) {
241 
242       if ((isTruthy(mHeader.flags & ResStringPool_header.UTF8_FLAG)
243               && (mHeader.getByte(mStrings + mStringPoolSize - 1) != 0))
244           || (!isTruthy(mHeader.flags & ResStringPool_header.UTF8_FLAG)
245               && (mHeader.getShort(mStrings + mStringPoolSize * 2 - 2) != 0))) {
246         ALOGW("Bad string block: last string is not 0-terminated\n");
247         return (mError=BAD_TYPE);
248       }
249     } else {
250       mStrings = -1;
251       mStringPoolSize = 0;
252     }
253 
254     if (mHeader.styleCount > 0) {
255       mEntryStyles = new IntArray(mEntries.myBuf(), mEntries.myOffset() + mHeader.stringCount * SIZEOF_INT);
256       // invariant: integer overflow in calculating mEntryStyles
257       if (mEntryStyles.myOffset() < mEntries.myOffset()) {
258         ALOGW("Bad string block: integer overflow finding styles\n");
259         return (mError=BAD_TYPE);
260       }
261 
262 //      if (((const uint8_t*)mEntryStyles-(const uint8_t*)mHeader) > (int)size) {
263       if ((mEntryStyles.myOffset() - mHeader.myOffset()) > (int)size) {
264         ALOGW(
265             "Bad string block: entry of %d styles extends past data size %d\n",
266             (int) mEntryStyles.myOffset(), (int) size);
267         return (mError=BAD_TYPE);
268       }
269       mStyles = mHeader.stylesStart;
270       if (mHeader.stylesStart >= mHeader.header.size) {
271         ALOGW("Bad string block: style pool starts %d, after total size %d\n",
272             (int)mHeader.stylesStart, (int)mHeader.header.size);
273         return (mError=BAD_TYPE);
274       }
275       mStylePoolSize =
276           (mHeader.header.size-mHeader.stylesStart) /* / sizeof(uint32_t)*/;
277 
278 //      if (notDeviceEndian) {
279 //        size_t i;
280 //        uint32_t* e = final_cast<uint32_t*>(mEntryStyles);
281 //        for (i=0; i<mHeader.styleCount; i++) {
282 //          e[i] = dtohl(mEntryStyles[i]);
283 //        }
284 //        uint32_t* s = final_cast<uint32_t*>(mStyles);
285 //        for (i=0; i<mStylePoolSize; i++) {
286 //          s[i] = dtohl(mStyles[i]);
287 //        }
288 //      }
289 
290 //        final ResStringPool_span endSpan = {
291 //          { htodl(ResStringPool_span.END) },
292 //          htodl(ResStringPool_span.END), htodl(ResStringPool_span.END)
293 //      };
294 //      if (memcmp(&mStyles[mStylePoolSize-(sizeof(endSpan)/sizeof(uint32_t))],
295 //                   &endSpan, sizeof(endSpan)) != 0) {
296       ResStringPool_span endSpan = new ResStringPool_span(buf,
297           mHeader.myOffset() + mStyles + (mStylePoolSize - ResStringPool_span.SIZEOF /* / 4 */));
298       if (!endSpan.isEnd()) {
299         ALOGW("Bad string block: last style is not 0xFFFFFFFF-terminated\n");
300         return (mError=BAD_TYPE);
301       }
302     } else {
303       mEntryStyles = null;
304       mStyles = 0;
305       mStylePoolSize = 0;
306     }
307 
308     return (mError=NO_ERROR);
309   }
310 
311 //  public void setTo(XmlResStringPool xmlStringPool) {
312 //    this.mHeader = new ResStringPoolHeader();
313 //    this.mStrings = new ArrayList<>();
314 //    Collections.addAll(mStrings, xmlStringPool.strings());
315 //  }
316 
setError(int error)317   private int setError(int error) {
318     mError = error;
319     return mError;
320   }
321 
uninit()322   void uninit() {
323     setError(NO_INIT);
324     mHeader = null;
325   }
326 
stringAt(int idx)327   public String stringAt(int idx) {
328     if (mError == NO_ERROR && idx < mHeader.stringCount) {
329         final boolean isUTF8 = (mHeader.flags&ResStringPool_header.UTF8_FLAG) != 0;
330 //        const uint32_t off = mEntries[idx]/(isUTF8?sizeof(uint8_t):sizeof(uint16_t));
331       ByteBuffer buf = mHeader.myBuf();
332       int bufOffset = mHeader.myOffset();
333       // const uint32_t off = mEntries[idx]/(isUTF8?sizeof(uint8_t):sizeof(uint16_t));
334       final int off = mEntries.get(idx)
335             /(isUTF8?1/*sizeof(uint8_t)*/:2/*sizeof(uint16_t)*/);
336       if (off < (mStringPoolSize-1)) {
337         if (!isUTF8) {
338           final int strings = mStrings;
339           final int str = strings+off*2;
340           return decodeString(buf, bufOffset + str, ResourceString.Type.UTF16);
341 //          int u16len = decodeLengthUTF16(buf, bufOffset + str);
342 //          if ((str+u16len*2-strings) < mStringPoolSize) {
343 //            // Reject malformed (non null-terminated) strings
344 //            if (buf.getShort(bufOffset + str + u16len*2) != 0x0000) {
345 //              ALOGW("Bad string block: string #%d is not null-terminated",
346 //                  (int)idx);
347 //              return null;
348 //            }
349 //            byte[] bytes = new byte[u16len * 2];
350 //            buf.position(bufOffset + str);
351 //            buf.get(bytes);
352 //               // Reject malformed (non null-terminated) strings
353 //               if (str[encLen] != 0x00) {
354 //                   ALOGW("Bad string block: string #%d is not null-terminated",
355 //                         (int)idx);
356 //                   return NULL;
357 //               }
358 //            return new String(bytes, StandardCharsets.UTF_16);
359 //          } else {
360 //            ALOGW("Bad string block: string #%d extends to %d, past end at %d\n",
361 //                (int)idx, (int)(str+u16len-strings), (int)mStringPoolSize);
362 //          }
363         } else {
364           final int strings = mStrings;
365           final int u8str = strings+off;
366           return decodeString(buf, bufOffset + u8str, ResourceString.Type.UTF8);
367 
368 //                *u16len = decodeLength(&u8str);
369 //          size_t u8len = decodeLength(&u8str);
370 //
371 //          // encLen must be less than 0x7FFF due to encoding.
372 //          if ((uint32_t)(u8str+u8len-strings) < mStringPoolSize) {
373 //            AutoMutex lock(mDecodeLock);
374 //
375 //            if (mCache != NULL && mCache[idx] != NULL) {
376 //              return mCache[idx];
377 //            }
378 //
379 //            // Retrieve the actual length of the utf8 string if the
380 //            // encoded length was truncated
381 //            if (stringDecodeAt(idx, u8str, u8len, &u8len) == NULL) {
382 //                return NULL;
383 //            }
384 //
385 //            // Since AAPT truncated lengths longer than 0x7FFF, check
386 //            // that the bits that remain after truncation at least match
387 //            // the bits of the actual length
388 //            ssize_t actualLen = utf8_to_utf16_length(u8str, u8len);
389 //            if (actualLen < 0 || ((size_t)actualLen & 0x7FFF) != *u16len) {
390 //              ALOGW("Bad string block: string #%lld decoded length is not correct "
391 //                  "%lld vs %llu\n",
392 //                  (long long)idx, (long long)actualLen, (long long)*u16len);
393 //              return NULL;
394 //            }
395 //
396 //            utf8_to_utf16(u8str, u8len, u16str, *u16len + 1);
397 //
398 //            if (mCache == NULL) {
399 // #ifndef __ANDROID__
400 //                if (kDebugStringPoolNoisy) {
401 //                    ALOGI("CREATING STRING CACHE OF %zu bytes",
402 //                          mHeader->stringCount*sizeof(char16_t**));
403 //                }
404 // #else
405 //                // We do not want to be in this case when actually running Android.
406 //                ALOGW("CREATING STRING CACHE OF %zu bytes",
407 //                        static_cast<size_t>(mHeader->stringCount*sizeof(char16_t**)));
408 // #endif
409 //                mCache = (char16_t**)calloc(mHeader->stringCount, sizeof(char16_t*));
410 //                if (mCache == NULL) {
411 //                    ALOGW("No memory trying to allocate decode cache table of %d bytes\n",
412 //                          (int)(mHeader->stringCount*sizeof(char16_t**)));
413 //                    return NULL;
414 //                }
415 //            }
416 //            *u16len = (size_t) actualLen;
417 //            char16_t *u16str = (char16_t *)calloc(*u16len+1, sizeof(char16_t));
418 //            if (!u16str) {
419 //              ALOGW("No memory when trying to allocate decode cache for string #%d\n",
420 //                  (int)idx);
421 //              return NULL;
422 //            }
423 //
424 //            if (kDebugStringPoolNoisy) {
425 //              ALOGI("Caching UTF8 string: %s", u8str);
426 //            }
427 //
428 //            mCache[idx] = u16str;
429 //            return u16str;
430 //          } else {
431 //            ALOGW("Bad string block: string #%lld extends to %lld, past end at %lld\n",
432 //                (long long)idx, (long long)(u8str+u8len-strings),
433 //                (long long)mStringPoolSize);
434 //          }
435         }
436       } else {
437         ALOGW("Bad string block: string #%d entry is at %d, past end at %d\n",
438             (int)idx, (int)(off*2/*sizeof(uint16_t)*/),
439             (int)(mStringPoolSize*2/*sizeof(uint16_t)*/));
440       }
441     }
442     return null;
443   }
444 
stringAt(int idx, Ref<Integer> outLen)445   String stringAt(int idx, Ref<Integer> outLen) {
446     String s = stringAt(idx);
447     if (s != null && outLen != null) {
448       outLen.set(s.length());
449     }
450     return s;
451   }
452 
string8At(int id, Ref<Integer> outLen)453   public String string8At(int id, Ref<Integer> outLen) {
454     return stringAt(id, outLen);
455   }
456 
styleAt(final ResStringPool_ref ref)457   final ResStringPool_span styleAt(final ResStringPool_ref ref) {
458     return styleAt(ref.index);
459   }
460 
styleAt(int idx)461   public final ResStringPool_span styleAt(int idx) {
462     if (mError == NO_ERROR && idx < mHeader.styleCount) {
463       // const uint32_t off = (mEntryStyles[idx]/sizeof(uint32_t));
464       final int off = mEntryStyles.get(idx) / SIZEOF_INT;
465       if (off < mStylePoolSize) {
466         // return (const ResStringPool_span*)(mStyles+off);
467         return new ResStringPool_span(
468             mHeader.myBuf(), mHeader.myOffset() + mStyles + off * SIZEOF_INT);
469       } else {
470         ALOGW("Bad string block: style #%d entry is at %d, past end at %d\n",
471             (int)idx, (int)(off*SIZEOF_INT),
472             (int)(mStylePoolSize*SIZEOF_INT));
473       }
474     }
475     return null;
476   }
477 
indexOfString(String str)478   public int indexOfString(String str) {
479     if (mError != NO_ERROR) {
480       return mError;
481     }
482 
483     if (kDebugStringPoolNoisy) {
484       ALOGI("indexOfString : %s", str);
485     }
486 
487     if ( (mHeader.flags&ResStringPoolHeader.SORTED_FLAG) != 0) {
488       // Do a binary search for the string...  this is a little tricky,
489       // because the strings are sorted with strzcmp16().  So to match
490       // the ordering, we need to convert strings in the pool to UTF-16.
491       // But we don't want to hit the cache, so instead we will have a
492       // local temporary allocation for the conversions.
493       int l = 0;
494       int h = mHeader.stringCount-1;
495 
496       int mid;
497       while (l <= h) {
498         mid = l + (h - l)/2;
499         String s = stringAt(mid);
500         int c = s != null ? s.compareTo(str) : -1;
501         if (kDebugStringPoolNoisy) {
502           ALOGI("Looking at %s, cmp=%d, l/mid/h=%d/%d/%d\n",
503               s, c, (int)l, (int)mid, (int)h);
504         }
505         if (c == 0) {
506           if (kDebugStringPoolNoisy) {
507             ALOGI("MATCH!");
508           }
509           return mid;
510         } else if (c < 0) {
511           l = mid + 1;
512         } else {
513           h = mid - 1;
514         }
515       }
516     } else {
517       // It is unusual to get the ID from an unsorted string block...
518       // most often this happens because we want to get IDs for style
519       // span tags; since those always appear at the end of the string
520       // block, start searching at the back.
521       for (int i = mHeader.stringCount; i>=0; i--) {
522         String s = stringAt(i);
523         if (kDebugStringPoolNoisy) {
524           ALOGI("Looking at %s, i=%d\n", s, i);
525         }
526         if (Objects.equals(s, str)) {
527           if (kDebugStringPoolNoisy) {
528             ALOGI("MATCH!");
529           }
530           return i;
531         }
532       }
533     }
534 
535     return NAME_NOT_FOUND;
536   }
537 //
size()538     public int size() {
539       return mError == NO_ERROR ? mHeader.stringCount : 0;
540     }
541 
styleCount()542     int styleCount() {
543       return mError == NO_ERROR ? mHeader.styleCount : 0;
544     }
545 
bytes()546     int bytes() {
547       return mError == NO_ERROR ? mHeader.header.size : 0;
548     }
549 
isUTF8()550   public boolean isUTF8() {
551     return true;
552   }
553 
getError()554   public int getError() {
555     return mError;
556   }
557 
558 //    int styleCount() final;
559 //    int bytes() final;
560 //
561 //    boolean isSorted() final;
562 //    boolean isUTF8() final;
563 //
564 
565 }
566