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